Merge remote-tracking branch 'origin/main' into feat/extend-models-support

This commit is contained in:
Kacper
2025-12-10 14:57:37 +01:00
16 changed files with 2919 additions and 168 deletions

View File

@@ -44,6 +44,47 @@ export interface StatResult {
// Auto Mode types - Import from electron.d.ts to avoid duplication
import type { AutoModeEvent, ModelDefinition, ProviderStatus, WorktreeAPI, GitAPI, WorktreeInfo, WorktreeStatus, FileDiffsResult, FileDiffResult, FileStatus } from "@/types/electron";
// Feature Suggestions types
export interface FeatureSuggestion {
id: string;
category: string;
description: string;
steps: string[];
priority: number;
reasoning: string;
}
export interface SuggestionsEvent {
type: "suggestions_progress" | "suggestions_tool" | "suggestions_complete" | "suggestions_error";
content?: string;
tool?: string;
input?: unknown;
suggestions?: FeatureSuggestion[];
error?: string;
}
export interface SuggestionsAPI {
generate: (projectPath: string) => Promise<{ success: boolean; error?: string }>;
stop: () => Promise<{ success: boolean; error?: string }>;
status: () => Promise<{ success: boolean; isRunning?: boolean; error?: string }>;
onEvent: (callback: (event: SuggestionsEvent) => void) => () => void;
}
// Spec Regeneration types
export type SpecRegenerationEvent =
| { type: "spec_regeneration_progress"; content: string }
| { type: "spec_regeneration_tool"; tool: string; input: unknown }
| { type: "spec_regeneration_complete"; message: string }
| { type: "spec_regeneration_error"; error: string };
export interface SpecRegenerationAPI {
create: (projectPath: string, projectOverview: string, generateFeatures?: boolean) => Promise<{ success: boolean; error?: string }>;
generate: (projectPath: string, projectDefinition: string) => Promise<{ success: boolean; error?: string }>;
stop: () => Promise<{ success: boolean; error?: string }>;
status: () => Promise<{ success: boolean; isRunning?: boolean; error?: string }>;
onEvent: (callback: (event: SpecRegenerationEvent) => void) => () => void;
}
export interface AutoModeAPI {
start: (projectPath: string, maxConcurrency?: number) => Promise<{ success: boolean; error?: string }>;
stop: () => Promise<{ success: boolean; error?: string }>;
@@ -79,7 +120,6 @@ export interface ElectronAPI {
trashItem?: (filePath: string) => Promise<WriteResult>;
getPath: (name: string) => Promise<string>;
saveImageToTemp?: (data: string, filename: string, mimeType: string, projectPath?: string) => Promise<SaveImageResult>;
autoMode?: AutoModeAPI;
checkClaudeCli?: () => Promise<{
success: boolean;
status?: string;
@@ -130,6 +170,8 @@ export interface ElectronAPI {
}>;
worktree?: WorktreeAPI;
git?: GitAPI;
suggestions?: SuggestionsAPI;
specRegeneration?: SpecRegenerationAPI;
}
// Note: Window interface is declared in @/types/electron.d.ts
@@ -427,6 +469,12 @@ export const getElectronAPI = (): ElectronAPI => {
// Mock Git API (for non-worktree operations)
git: createMockGitAPI(),
// Mock Suggestions API
suggestions: createMockSuggestionsAPI(),
// Mock Spec Regeneration API
specRegeneration: createMockSpecRegenerationAPI(),
};
};
@@ -924,6 +972,381 @@ function delay(ms: number, featureId: string): Promise<void> {
});
}
// Mock Suggestions state and implementation
let mockSuggestionsRunning = false;
let mockSuggestionsCallbacks: ((event: SuggestionsEvent) => void)[] = [];
let mockSuggestionsTimeout: NodeJS.Timeout | null = null;
function createMockSuggestionsAPI(): SuggestionsAPI {
return {
generate: async (projectPath: string) => {
if (mockSuggestionsRunning) {
return { success: false, error: "Suggestions generation is already running" };
}
mockSuggestionsRunning = true;
console.log(`[Mock] Generating suggestions for: ${projectPath}`);
// Simulate async suggestion generation
simulateSuggestionsGeneration();
return { success: true };
},
stop: async () => {
mockSuggestionsRunning = false;
if (mockSuggestionsTimeout) {
clearTimeout(mockSuggestionsTimeout);
mockSuggestionsTimeout = null;
}
return { success: true };
},
status: async () => {
return {
success: true,
isRunning: mockSuggestionsRunning,
};
},
onEvent: (callback: (event: SuggestionsEvent) => void) => {
mockSuggestionsCallbacks.push(callback);
return () => {
mockSuggestionsCallbacks = mockSuggestionsCallbacks.filter(cb => cb !== callback);
};
},
};
}
function emitSuggestionsEvent(event: SuggestionsEvent) {
mockSuggestionsCallbacks.forEach(cb => cb(event));
}
async function simulateSuggestionsGeneration() {
// Emit progress events
emitSuggestionsEvent({
type: "suggestions_progress",
content: "Starting project analysis...\n",
});
await new Promise(resolve => {
mockSuggestionsTimeout = setTimeout(resolve, 500);
});
if (!mockSuggestionsRunning) return;
emitSuggestionsEvent({
type: "suggestions_tool",
tool: "Glob",
input: { pattern: "**/*.{ts,tsx,js,jsx}" },
});
await new Promise(resolve => {
mockSuggestionsTimeout = setTimeout(resolve, 500);
});
if (!mockSuggestionsRunning) return;
emitSuggestionsEvent({
type: "suggestions_progress",
content: "Analyzing codebase structure...\n",
});
await new Promise(resolve => {
mockSuggestionsTimeout = setTimeout(resolve, 500);
});
if (!mockSuggestionsRunning) return;
emitSuggestionsEvent({
type: "suggestions_progress",
content: "Identifying missing features...\n",
});
await new Promise(resolve => {
mockSuggestionsTimeout = setTimeout(resolve, 500);
});
if (!mockSuggestionsRunning) return;
// Generate mock suggestions
const mockSuggestions: FeatureSuggestion[] = [
{
id: `suggestion-${Date.now()}-0`,
category: "User Experience",
description: "Add dark mode toggle with system preference detection",
steps: [
"Create a ThemeProvider context to manage theme state",
"Add a toggle component in the settings or header",
"Implement CSS variables for theme colors",
"Add localStorage persistence for user preference"
],
priority: 1,
reasoning: "Dark mode is a standard feature that improves accessibility and user comfort"
},
{
id: `suggestion-${Date.now()}-1`,
category: "Performance",
description: "Implement lazy loading for heavy components",
steps: [
"Identify components that are heavy or rarely used",
"Use React.lazy() and Suspense for code splitting",
"Add loading states for lazy-loaded components"
],
priority: 2,
reasoning: "Improves initial load time and reduces bundle size"
},
{
id: `suggestion-${Date.now()}-2`,
category: "Accessibility",
description: "Add keyboard navigation support throughout the app",
steps: [
"Implement focus management for modals and dialogs",
"Add keyboard shortcuts for common actions",
"Ensure all interactive elements are focusable",
"Add ARIA labels and roles where needed"
],
priority: 3,
reasoning: "Improves accessibility for users who rely on keyboard navigation"
},
{
id: `suggestion-${Date.now()}-3`,
category: "Testing",
description: "Add comprehensive unit test coverage",
steps: [
"Set up Jest and React Testing Library",
"Create tests for all utility functions",
"Add component tests for critical UI elements",
"Set up CI pipeline for automated testing"
],
priority: 4,
reasoning: "Ensures code quality and prevents regressions"
},
{
id: `suggestion-${Date.now()}-4`,
category: "Developer Experience",
description: "Add Storybook for component documentation",
steps: [
"Install and configure Storybook",
"Create stories for all UI components",
"Add interaction tests using play functions",
"Set up Chromatic for visual regression testing"
],
priority: 5,
reasoning: "Improves component development workflow and documentation"
}
];
emitSuggestionsEvent({
type: "suggestions_complete",
suggestions: mockSuggestions,
});
mockSuggestionsRunning = false;
mockSuggestionsTimeout = null;
}
// Mock Spec Regeneration state and implementation
let mockSpecRegenerationRunning = false;
let mockSpecRegenerationCallbacks: ((event: SpecRegenerationEvent) => void)[] = [];
let mockSpecRegenerationTimeout: NodeJS.Timeout | null = null;
function createMockSpecRegenerationAPI(): SpecRegenerationAPI {
return {
create: async (projectPath: string, projectOverview: string, generateFeatures = true) => {
if (mockSpecRegenerationRunning) {
return { success: false, error: "Spec creation is already running" };
}
mockSpecRegenerationRunning = true;
console.log(`[Mock] Creating initial spec for: ${projectPath}, generateFeatures: ${generateFeatures}`);
// Simulate async spec creation
simulateSpecCreation(projectPath, projectOverview, generateFeatures);
return { success: true };
},
generate: async (projectPath: string, projectDefinition: string) => {
if (mockSpecRegenerationRunning) {
return { success: false, error: "Spec regeneration is already running" };
}
mockSpecRegenerationRunning = true;
console.log(`[Mock] Regenerating spec for: ${projectPath}`);
// Simulate async spec regeneration
simulateSpecRegeneration(projectPath, projectDefinition);
return { success: true };
},
stop: async () => {
mockSpecRegenerationRunning = false;
if (mockSpecRegenerationTimeout) {
clearTimeout(mockSpecRegenerationTimeout);
mockSpecRegenerationTimeout = null;
}
return { success: true };
},
status: async () => {
return {
success: true,
isRunning: mockSpecRegenerationRunning,
};
},
onEvent: (callback: (event: SpecRegenerationEvent) => void) => {
mockSpecRegenerationCallbacks.push(callback);
return () => {
mockSpecRegenerationCallbacks = mockSpecRegenerationCallbacks.filter(cb => cb !== callback);
};
},
};
}
function emitSpecRegenerationEvent(event: SpecRegenerationEvent) {
mockSpecRegenerationCallbacks.forEach(cb => cb(event));
}
async function simulateSpecCreation(projectPath: string, projectOverview: string, generateFeatures = true) {
emitSpecRegenerationEvent({
type: "spec_regeneration_progress",
content: "Starting project analysis...\n",
});
await new Promise(resolve => {
mockSpecRegenerationTimeout = setTimeout(resolve, 500);
});
if (!mockSpecRegenerationRunning) return;
emitSpecRegenerationEvent({
type: "spec_regeneration_tool",
tool: "Glob",
input: { pattern: "**/*.{json,ts,tsx}" },
});
await new Promise(resolve => {
mockSpecRegenerationTimeout = setTimeout(resolve, 500);
});
if (!mockSpecRegenerationRunning) return;
emitSpecRegenerationEvent({
type: "spec_regeneration_progress",
content: "Detecting tech stack...\n",
});
await new Promise(resolve => {
mockSpecRegenerationTimeout = setTimeout(resolve, 500);
});
if (!mockSpecRegenerationRunning) return;
// Write mock app_spec.txt
mockFileSystem[`${projectPath}/.automaker/app_spec.txt`] = `<project_specification>
<project_name>Demo Project</project_name>
<overview>
${projectOverview}
</overview>
<technology_stack>
<frontend>
<framework>Next.js</framework>
<ui_library>React</ui_library>
<styling>Tailwind CSS</styling>
</frontend>
</technology_stack>
<core_capabilities>
<feature_1>Core functionality based on overview</feature_1>
</core_capabilities>
<implementation_roadmap>
<phase_1_foundation>Setup and basic structure</phase_1_foundation>
<phase_2_core_logic>Core features implementation</phase_2_core_logic>
</implementation_roadmap>
</project_specification>`;
// If generateFeatures is true, also write feature_list.json
if (generateFeatures) {
const timestamp = Date.now();
mockFileSystem[`${projectPath}/.automaker/feature_list.json`] = JSON.stringify([
{
id: `feature-${timestamp}-0`,
category: "Phase 1: Foundation",
description: "Setup and basic structure",
status: "backlog",
steps: ["Initialize project", "Configure dependencies"],
skipTests: true,
},
{
id: `feature-${timestamp}-1`,
category: "Phase 2: Core Logic",
description: "Core features implementation",
status: "backlog",
steps: ["Implement core functionality", "Add business logic"],
skipTests: true,
},
], null, 2);
}
emitSpecRegenerationEvent({
type: "spec_regeneration_complete",
message: "Initial spec creation complete!",
});
mockSpecRegenerationRunning = false;
mockSpecRegenerationTimeout = null;
}
async function simulateSpecRegeneration(projectPath: string, projectDefinition: string) {
emitSpecRegenerationEvent({
type: "spec_regeneration_progress",
content: "Starting spec regeneration...\n",
});
await new Promise(resolve => {
mockSpecRegenerationTimeout = setTimeout(resolve, 500);
});
if (!mockSpecRegenerationRunning) return;
emitSpecRegenerationEvent({
type: "spec_regeneration_progress",
content: "Analyzing codebase...\n",
});
await new Promise(resolve => {
mockSpecRegenerationTimeout = setTimeout(resolve, 500);
});
if (!mockSpecRegenerationRunning) return;
// Write regenerated spec
mockFileSystem[`${projectPath}/.automaker/app_spec.txt`] = `<project_specification>
<project_name>Regenerated Project</project_name>
<overview>
${projectDefinition}
</overview>
<technology_stack>
<frontend>
<framework>Next.js</framework>
<ui_library>React</ui_library>
<styling>Tailwind CSS</styling>
</frontend>
</technology_stack>
<core_capabilities>
<feature_1>Regenerated features based on definition</feature_1>
</core_capabilities>
</project_specification>`;
emitSpecRegenerationEvent({
type: "spec_regeneration_complete",
message: "Spec regeneration complete!",
});
mockSpecRegenerationRunning = false;
mockSpecRegenerationTimeout = null;
}
// Utility functions for project management
export interface Project {