diff --git a/apps/frontend/src/__tests__/integration/subprocess-spawn.test.ts b/apps/frontend/src/__tests__/integration/subprocess-spawn.test.ts index 6dd99107..a057276b 100644 --- a/apps/frontend/src/__tests__/integration/subprocess-spawn.test.ts +++ b/apps/frontend/src/__tests__/integration/subprocess-spawn.test.ts @@ -297,7 +297,7 @@ describe('Subprocess Spawn Integration', () => { // Simulate stdout data (must include newline for buffered output processing) mockStdout.emit('data', Buffer.from('Test log output\n')); - expect(logHandler).toHaveBeenCalledWith('task-1', 'Test log output\n', undefined); + expect(logHandler).toHaveBeenCalledWith('task-1', 'Test log output\n'); }, 30000); // Increase timeout for Windows CI (dynamic imports are slow) it('should emit log events from stderr', async () => { @@ -313,7 +313,7 @@ describe('Subprocess Spawn Integration', () => { // Simulate stderr data (must include newline for buffered output processing) mockStderr.emit('data', Buffer.from('Progress: 50%\n')); - expect(logHandler).toHaveBeenCalledWith('task-1', 'Progress: 50%\n', undefined); + expect(logHandler).toHaveBeenCalledWith('task-1', 'Progress: 50%\n'); }, 30000); // Increase timeout for Windows CI (dynamic imports are slow) it('should emit exit event when process exits', async () => { @@ -329,8 +329,8 @@ describe('Subprocess Spawn Integration', () => { // Simulate process exit mockProcess.emit('exit', 0); - // Exit event includes taskId, exit code, process type, and optional projectId - expect(exitHandler).toHaveBeenCalledWith('task-1', 0, expect.any(String), undefined); + // Exit event includes taskId, exit code, and process type + expect(exitHandler).toHaveBeenCalledWith('task-1', 0, expect.any(String)); }, 30000); // Increase timeout for Windows CI (dynamic imports are slow) it('should emit error event when process errors', async () => { @@ -346,7 +346,7 @@ describe('Subprocess Spawn Integration', () => { // Simulate process error mockProcess.emit('error', new Error('Spawn failed')); - expect(errorHandler).toHaveBeenCalledWith('task-1', 'Spawn failed', undefined); + expect(errorHandler).toHaveBeenCalledWith('task-1', 'Spawn failed'); }, 30000); // Increase timeout for Windows CI (dynamic imports are slow) it('should kill task and remove from tracking', async () => { diff --git a/apps/frontend/src/main/agent/agent-queue.ts b/apps/frontend/src/main/agent/agent-queue.ts index 2c75a184..a2157588 100644 --- a/apps/frontend/src/main/agent/agent-queue.ts +++ b/apps/frontend/src/main/agent/agent-queue.ts @@ -1,6 +1,6 @@ import { spawn } from 'child_process'; import path from 'path'; -import { existsSync, mkdirSync, unlinkSync, promises as fsPromises } from 'fs'; +import { existsSync, writeFileSync, mkdirSync, unlinkSync, promises as fsPromises } from 'fs'; import { EventEmitter } from 'events'; import { AgentState } from './agent-state'; import { AgentEvents } from './agent-events'; @@ -8,7 +8,7 @@ import { AgentProcessManager } from './agent-process'; import { RoadmapConfig } from './types'; import type { IdeationConfig, Idea } from '../../shared/types'; import { AUTO_BUILD_PATHS } from '../../shared/constants'; -import { detectRateLimit, createSDKRateLimitInfo, getBestAvailableProfileEnv } from '../rate-limit-detector'; +import { detectRateLimit, createSDKRateLimitInfo, getProfileEnv } from '../rate-limit-detector'; import { getAPIProfileEnv } from '../services/profile'; import { getOAuthModeClearVars } from './env-utils'; import { debugLog, debugError } from '../../shared/utils/debug-logger'; @@ -113,14 +113,14 @@ export class AgentQueueManager { * @param startedAt - When generation started (ISO string) * @param isRunning - Whether generation is actively running */ - private async persistRoadmapProgress( + private persistRoadmapProgress( projectPath: string, phase: string, progress: number, message: string, startedAt: string, isRunning: boolean - ): Promise { + ): void { try { const roadmapDir = path.join(projectPath, AUTO_BUILD_PATHS.ROADMAP_DIR); const progressPath = path.join(roadmapDir, AUTO_BUILD_PATHS.GENERATION_PROGRESS); @@ -139,7 +139,7 @@ export class AgentQueueManager { is_running: isRunning }; - await writeFileWithRetry(progressPath, JSON.stringify(progressData, null, 2), { encoding: 'utf-8' }); + writeFileSync(progressPath, JSON.stringify(progressData, null, 2)); debugLog('[Agent Queue] Persisted roadmap progress:', { phase, progress }); } catch (err) { debugError('[Agent Queue] Failed to persist roadmap progress:', err); @@ -153,9 +153,6 @@ export class AgentQueueManager { * @param projectPath - The project directory path */ private clearRoadmapProgress(projectPath: string): void { - // Cancel any pending debounced write to prevent re-creating the file after deletion - this.cancelPersistRoadmapProgress(); - try { const progressPath = path.join( projectPath, @@ -775,8 +772,8 @@ export class AgentQueueManager { // Track startedAt timestamp for progress persistence const roadmapStartedAt = new Date().toISOString(); - // Persist initial progress state (debounced - will execute immediately due to leading: true) - this.debouncedPersistRoadmapProgress( + // Persist initial progress state + this.persistRoadmapProgress( projectPath, progressPhase, progressPercent, @@ -813,8 +810,8 @@ export class AgentQueueManager { // Get status message for display const statusMessage = formatStatusMessage(log); - // Persist progress to disk for recovery after restart (debounced to limit writes) - this.debouncedPersistRoadmapProgress( + // Persist progress to disk for recovery after restart + this.persistRoadmapProgress( projectPath, progressPhase, progressPercent, @@ -841,8 +838,8 @@ export class AgentQueueManager { const statusMessage = formatStatusMessage(log); - // Persist progress to disk (debounced - also on stderr to show activity) - this.debouncedPersistRoadmapProgress( + // Persist progress to disk (also on stderr to show activity) + this.persistRoadmapProgress( projectPath, progressPhase, progressPercent, diff --git a/apps/frontend/src/main/ipc-handlers/roadmap-handlers.ts b/apps/frontend/src/main/ipc-handlers/roadmap-handlers.ts index 7136660a..81f64ede 100644 --- a/apps/frontend/src/main/ipc-handlers/roadmap-handlers.ts +++ b/apps/frontend/src/main/ipc-handlers/roadmap-handlers.ts @@ -21,7 +21,7 @@ import type { } from "../../shared/types"; import type { RoadmapConfig } from "../agent/types"; import path from "path"; -import { readFileSync, writeFileSync, mkdirSync, readdirSync, unlinkSync } from "fs"; +import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, unlinkSync } from "fs"; import { projectStore } from "../project-store"; import { AgentManager } from "../agent"; import { debugLog, debugError } from "../../shared/utils/debug-logger"; @@ -690,8 +690,10 @@ ${(feature.acceptance_criteria || []).map((c: string) => `- [ ] ${c}`).join("\n" const progressPath = path.join(roadmapDir, AUTO_BUILD_PATHS.GENERATION_PROGRESS); try { - // Ensure roadmap directory exists (mkdirSync with recursive: true doesn't error if exists) - mkdirSync(roadmapDir, { recursive: true }); + // Ensure roadmap directory exists + if (!existsSync(roadmapDir)) { + mkdirSync(roadmapDir, { recursive: true }); + } // Derive isRunning from phase (active phases are running) const isRunning = progressData.phase !== 'idle' && progressData.phase !== 'complete' && progressData.phase !== 'error'; @@ -706,7 +708,7 @@ ${(feature.acceptance_criteria || []).map((c: string) => `- [ ] ${c}`).join("\n" is_running: isRunning, }; - await writeFileWithRetry(progressPath, JSON.stringify(fileData, null, 2), { encoding: 'utf-8' }); + writeFileSync(progressPath, JSON.stringify(fileData, null, 2)); debugLog("[Roadmap Handler] Saved progress checkpoint:", { projectId, phase: progressData.phase }); return { success: true }; @@ -737,8 +739,12 @@ ${(feature.acceptance_criteria || []).map((c: string) => `- [ ] ${c}`).join("\n" AUTO_BUILD_PATHS.GENERATION_PROGRESS ); + if (!existsSync(progressPath)) { + return { success: true, data: null }; + } + try { - const content = await readFileWithRetry(progressPath, { encoding: "utf-8" }) as string; + const content = readFileSync(progressPath, "utf-8"); const rawData = JSON.parse(content); // Valid phase values that the frontend expects @@ -763,10 +769,6 @@ ${(feature.acceptance_criteria || []).map((c: string) => `- [ ] ${c}`).join("\n" return { success: true, data: progressData }; } catch (error) { - // ENOENT (file not found) is expected - return null data - if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') { - return { success: true, data: null }; - } debugError("[Roadmap Handler] Failed to load progress:", error); return { success: false, @@ -791,21 +793,17 @@ ${(feature.acceptance_criteria || []).map((c: string) => `- [ ] ${c}`).join("\n" ); try { - // unlinkSync errors if file doesn't exist - catch and ignore ENOENT - unlinkSync(progressPath); - debugLog("[Roadmap Handler] Cleared progress checkpoint:", { projectId }); + if (existsSync(progressPath)) { + unlinkSync(progressPath); + debugLog("[Roadmap Handler] Cleared progress checkpoint:", { projectId }); + } return { success: true }; } catch (error) { - // ENOENT (file not found) is expected when clearing a non-existent progress file - if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') { - debugError("[Roadmap Handler] Failed to clear progress:", error); - return { - success: false, - error: error instanceof Error ? error.message : "Failed to clear progress", - }; - } - // File didn't exist - that's fine, consider it cleared - return { success: true }; + debugError("[Roadmap Handler] Failed to clear progress:", error); + return { + success: false, + error: error instanceof Error ? error.message : "Failed to clear progress", + }; } } ); diff --git a/apps/frontend/src/renderer/stores/roadmap-store.ts b/apps/frontend/src/renderer/stores/roadmap-store.ts index 9b43a550..85f616aa 100644 --- a/apps/frontend/src/renderer/stores/roadmap-store.ts +++ b/apps/frontend/src/renderer/stores/roadmap-store.ts @@ -283,7 +283,7 @@ export async function loadRoadmap(projectId: string): Promise { const parseDate = (dateStr: string | undefined): Date | undefined => { if (!dateStr) return undefined; const date = new Date(dateStr); - return Number.isNaN(date.getTime()) ? undefined : date; + return isNaN(date.getTime()) ? undefined : date; }; store.setGenerationStatus({ diff --git a/apps/frontend/src/shared/i18n/locales/en/common.json b/apps/frontend/src/shared/i18n/locales/en/common.json index 885fb2bc..3c668696 100644 --- a/apps/frontend/src/shared/i18n/locales/en/common.json +++ b/apps/frontend/src/shared/i18n/locales/en/common.json @@ -634,16 +634,6 @@ "goToSettings": "Go to Settings" } }, - "git": { - "branchGroups": { - "local": "Local Branches", - "remote": "Remote Branches" - }, - "branchType": { - "local": "Local", - "remote": "Remote" - } - }, "roadmapProgress": { "elapsedTime": "Elapsed", "lastActivity": "Last activity", @@ -683,31 +673,5 @@ "progress": "Progress", "lastActivityPrefix": "last activity", "lastProgressUpdateTooltip": "Last progress update received" - }, - "prStatus": { - "ci": { - "success": "CI Passed", - "pending": "CI Pending", - "failure": "CI Failed", - "successTooltip": "All CI checks have passed", - "pendingTooltip": "CI checks are still running", - "failureTooltip": "One or more CI checks have failed" - }, - "review": { - "approved": "Approved", - "changesRequested": "Changes Requested", - "pending": "Review Pending", - "approvedTooltip": "This PR has been approved", - "changesRequestedTooltip": "Changes have been requested on this PR", - "pendingTooltip": "Waiting for review" - }, - "merge": { - "ready": "Ready to Merge", - "blocked": "Merge Blocked", - "conflict": "Has Conflicts", - "readyTooltip": "This PR is ready to be merged", - "blockedTooltip": "This PR cannot be merged due to blocking conditions", - "conflictTooltip": "This PR has merge conflicts that need to be resolved" - } } } diff --git a/apps/frontend/src/shared/i18n/locales/fr/common.json b/apps/frontend/src/shared/i18n/locales/fr/common.json index fbe09d2f..f28c483b 100644 --- a/apps/frontend/src/shared/i18n/locales/fr/common.json +++ b/apps/frontend/src/shared/i18n/locales/fr/common.json @@ -634,16 +634,6 @@ "goToSettings": "Aller aux paramètres" } }, - "git": { - "branchGroups": { - "local": "Branches Locales", - "remote": "Branches Distantes" - }, - "branchType": { - "local": "Locale", - "remote": "Distante" - } - }, "roadmapProgress": { "elapsedTime": "Écoulé", "lastActivity": "Dernière activité", @@ -683,31 +673,5 @@ "progress": "Progression", "lastActivityPrefix": "dernière activité", "lastProgressUpdateTooltip": "Dernière mise à jour de progression reçue" - }, - "prStatus": { - "ci": { - "success": "CI réussie", - "pending": "CI en attente", - "failure": "CI échouée", - "successTooltip": "Toutes les vérifications CI ont réussi", - "pendingTooltip": "Les vérifications CI sont en cours", - "failureTooltip": "Une ou plusieurs vérifications CI ont échoué" - }, - "review": { - "approved": "Approuvée", - "changesRequested": "Modifications demandées", - "pending": "Révision en attente", - "approvedTooltip": "Cette PR a été approuvée", - "changesRequestedTooltip": "Des modifications ont été demandées sur cette PR", - "pendingTooltip": "En attente de révision" - }, - "merge": { - "ready": "Prête à fusionner", - "blocked": "Fusion bloquée", - "conflict": "Conflits détectés", - "readyTooltip": "Cette PR est prête à être fusionnée", - "blockedTooltip": "Cette PR ne peut pas être fusionnée en raison de conditions bloquantes", - "conflictTooltip": "Cette PR a des conflits de fusion qui doivent être résolus" - } } }