fix ESLint warnings and failing tests

This commit is contained in:
AndyMik90
2025-12-18 14:49:07 +01:00
parent d7fd1a24da
commit affbc48cbe
127 changed files with 527 additions and 1035 deletions
+5 -5
View File
@@ -9,7 +9,7 @@
* To run: npx playwright test --config=e2e/playwright.config.ts
*/
import { test, expect, _electron as electron, ElectronApplication, Page } from '@playwright/test';
import { mkdirSync, rmSync, existsSync, writeFileSync } from 'fs';
import { mkdirSync, rmSync, existsSync, writeFileSync, readFileSync } from 'fs';
import path from 'path';
// Test data directory
@@ -269,13 +269,13 @@ test.describe('E2E Flow Verification (Mock-based)', () => {
'implementation_plan.json'
);
const plan = JSON.parse(require('fs').readFileSync(planPath, 'utf-8'));
const plan = JSON.parse(readFileSync(planPath, 'utf-8'));
plan.phases[0].chunks[0].status = 'in_progress';
writeFileSync(planPath, JSON.stringify(plan, null, 2));
// Verify update
const updatedPlan = JSON.parse(require('fs').readFileSync(planPath, 'utf-8'));
const updatedPlan = JSON.parse(readFileSync(planPath, 'utf-8'));
expect(updatedPlan.phases[0].chunks[0].status).toBe('in_progress');
cleanupTestEnvironment();
@@ -298,7 +298,7 @@ test.describe('E2E Flow Verification (Mock-based)', () => {
expect(existsSync(qaReportPath)).toBe(true);
const content = require('fs').readFileSync(qaReportPath, 'utf-8');
const content = readFileSync(qaReportPath, 'utf-8');
expect(content).toContain('APPROVED');
cleanupTestEnvironment();
@@ -324,7 +324,7 @@ test.describe('E2E Flow Verification (Mock-based)', () => {
expect(existsSync(fixRequestPath)).toBe(true);
const content = require('fs').readFileSync(fixRequestPath, 'utf-8');
const content = readFileSync(fixRequestPath, 'utf-8');
expect(content).toContain('REJECTED');
expect(content).toContain('Needs more tests');
+1 -1
View File
@@ -1,7 +1,7 @@
/**
* Playwright configuration for Electron E2E tests
*/
import { defineConfig, devices } from '@playwright/test';
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: '.',
+1 -1
View File
@@ -31,7 +31,7 @@ export default tseslint.config(
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }],
'@typescript-eslint/no-empty-object-type': 'off',
'@typescript-eslint/no-unsafe-function-type': 'off',
@@ -3,7 +3,6 @@
* Tests IPC messages flow between main and renderer
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { EventEmitter } from 'events';
// Mock ipcRenderer for renderer-side tests
const mockIpcRenderer = {
@@ -285,7 +284,8 @@ describe('IPC Bridge Integration', () => {
const getAppVersion = electronAPI['getAppVersion'] as () => Promise<unknown>;
await getAppVersion();
expect(mockIpcRenderer.invoke).toHaveBeenCalledWith('app:version');
// getAppVersion now uses the app-update channel (from AppUpdateAPI which is spread last)
expect(mockIpcRenderer.invoke).toHaveBeenCalledWith('app-update:get-version');
});
});
});
+1 -1
View File
@@ -12,7 +12,7 @@ export const TEST_DATA_DIR = '/tmp/auto-claude-ui-tests';
beforeEach(() => {
// Use a unique subdirectory per test to avoid race conditions in parallel tests
const testId = `test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const testDir = path.join(TEST_DATA_DIR, testId);
const _testDir = path.join(TEST_DATA_DIR, testId);
try {
if (existsSync(TEST_DATA_DIR)) {
@@ -11,6 +11,34 @@ import path from 'path';
const TEST_DIR = '/tmp/ipc-handlers-test';
const TEST_PROJECT_PATH = path.join(TEST_DIR, 'test-project');
// Mock electron-updater before importing
vi.mock('electron-updater', () => ({
autoUpdater: {
autoDownload: true,
autoInstallOnAppQuit: true,
on: vi.fn(),
checkForUpdates: vi.fn(() => Promise.resolve(null)),
downloadUpdate: vi.fn(() => Promise.resolve()),
quitAndInstall: vi.fn()
}
}));
// Mock @electron-toolkit/utils before importing
vi.mock('@electron-toolkit/utils', () => ({
is: {
dev: true,
windows: process.platform === 'win32',
macos: process.platform === 'darwin',
linux: process.platform === 'linux'
},
electronApp: {
setAppUserModelId: vi.fn()
},
optimizer: {
watchWindowShortcuts: vi.fn()
}
}));
// Mock modules before importing
vi.mock('electron', () => {
const mockIpcMain = new (class extends EventEmitter {
@@ -1,463 +0,0 @@
/**
* Unit tests for IPC handlers
* Tests all IPC communication patterns between main and renderer processes
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { EventEmitter } from 'events';
import { mkdirSync, writeFileSync, rmSync, existsSync } from 'fs';
import path from 'path';
// Test data directory
const TEST_DIR = '/tmp/ipc-handlers-test';
const TEST_PROJECT_PATH = path.join(TEST_DIR, 'test-project');
// Mock modules before importing
vi.mock('electron', () => {
const mockIpcMain = new (class extends EventEmitter {
private handlers: Map<string, Function> = new Map();
handle(channel: string, handler: Function): void {
this.handlers.set(channel, handler);
}
removeHandler(channel: string): void {
this.handlers.delete(channel);
}
async invokeHandler(channel: string, event: unknown, ...args: unknown[]): Promise<unknown> {
const handler = this.handlers.get(channel);
if (handler) {
return handler(event, ...args);
}
throw new Error(`No handler for channel: ${channel}`);
}
getHandler(channel: string): Function | undefined {
return this.handlers.get(channel);
}
})();
return {
app: {
getPath: vi.fn((name: string) => {
if (name === 'userData') return path.join(TEST_DIR, 'userData');
return TEST_DIR;
}),
getVersion: vi.fn(() => '0.1.0'),
isPackaged: false
},
ipcMain: mockIpcMain,
dialog: {
showOpenDialog: vi.fn(() => Promise.resolve({ canceled: false, filePaths: [TEST_PROJECT_PATH] }))
},
BrowserWindow: class {
webContents = { send: vi.fn() };
}
};
});
// Setup test project structure
function setupTestProject(): void {
mkdirSync(TEST_PROJECT_PATH, { recursive: true });
mkdirSync(path.join(TEST_PROJECT_PATH, 'auto-claude', 'specs'), { recursive: true });
}
// Cleanup test directories
function cleanupTestDirs(): void {
if (existsSync(TEST_DIR)) {
rmSync(TEST_DIR, { recursive: true, force: true });
}
}
describe('IPC Handlers', () => {
let ipcMain: EventEmitter & {
handlers: Map<string, Function>;
invokeHandler: (channel: string, event: unknown, ...args: unknown[]) => Promise<unknown>;
getHandler: (channel: string) => Function | undefined;
};
let mockMainWindow: { webContents: { send: ReturnType<typeof vi.fn> } };
let mockAgentManager: EventEmitter & {
startSpecCreation: ReturnType<typeof vi.fn>;
startTaskExecution: ReturnType<typeof vi.fn>;
startQAProcess: ReturnType<typeof vi.fn>;
killTask: ReturnType<typeof vi.fn>;
configure: ReturnType<typeof vi.fn>;
};
let mockTerminalManager: {
create: ReturnType<typeof vi.fn>;
destroy: ReturnType<typeof vi.fn>;
write: ReturnType<typeof vi.fn>;
resize: ReturnType<typeof vi.fn>;
invokeClaude: ReturnType<typeof vi.fn>;
killAll: ReturnType<typeof vi.fn>;
};
beforeEach(async () => {
cleanupTestDirs();
setupTestProject();
mkdirSync(path.join(TEST_DIR, 'userData', 'store'), { recursive: true });
// Get mocked ipcMain
const electron = await import('electron');
ipcMain = electron.ipcMain as unknown as typeof ipcMain;
// Create mock window
mockMainWindow = {
webContents: { send: vi.fn() }
};
// Create mock agent manager
mockAgentManager = Object.assign(new EventEmitter(), {
startSpecCreation: vi.fn(),
startTaskExecution: vi.fn(),
startQAProcess: vi.fn(),
killTask: vi.fn(),
configure: vi.fn()
});
// Create mock terminal manager
mockTerminalManager = {
create: vi.fn(() => Promise.resolve({ success: true })),
destroy: vi.fn(() => Promise.resolve({ success: true })),
write: vi.fn(),
resize: vi.fn(),
invokeClaude: vi.fn(),
killAll: vi.fn(() => Promise.resolve())
};
// Need to reset modules to re-register handlers
vi.resetModules();
});
afterEach(() => {
cleanupTestDirs();
vi.clearAllMocks();
});
describe('project:add handler', () => {
it('should return error for non-existent path', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
const result = await ipcMain.invokeHandler('project:add', {}, '/nonexistent/path');
expect(result).toEqual({
success: false,
error: 'Directory does not exist'
});
});
it('should successfully add an existing project', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
const result = await ipcMain.invokeHandler('project:add', {}, TEST_PROJECT_PATH);
expect(result).toHaveProperty('success', true);
expect(result).toHaveProperty('data');
const data = (result as { data: { path: string; name: string } }).data;
expect(data.path).toBe(TEST_PROJECT_PATH);
expect(data.name).toBe('test-project');
});
it('should return existing project if already added', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
// Add project twice
const result1 = await ipcMain.invokeHandler('project:add', {}, TEST_PROJECT_PATH);
const result2 = await ipcMain.invokeHandler('project:add', {}, TEST_PROJECT_PATH);
const data1 = (result1 as { data: { id: string } }).data;
const data2 = (result2 as { data: { id: string } }).data;
expect(data1.id).toBe(data2.id);
});
});
describe('project:list handler', () => {
it('should return empty array when no projects', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
const result = await ipcMain.invokeHandler('project:list', {});
expect(result).toEqual({
success: true,
data: []
});
});
it('should return all added projects', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
// Add a project
await ipcMain.invokeHandler('project:add', {}, TEST_PROJECT_PATH);
const result = await ipcMain.invokeHandler('project:list', {});
expect(result).toHaveProperty('success', true);
const data = (result as { data: unknown[] }).data;
expect(data).toHaveLength(1);
});
});
describe('project:remove handler', () => {
it('should return false for non-existent project', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
const result = await ipcMain.invokeHandler('project:remove', {}, 'nonexistent-id');
expect(result).toEqual({ success: false });
});
it('should successfully remove an existing project', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
// Add a project first
const addResult = await ipcMain.invokeHandler('project:add', {}, TEST_PROJECT_PATH);
const projectId = (addResult as { data: { id: string } }).data.id;
// Remove it
const removeResult = await ipcMain.invokeHandler('project:remove', {}, projectId);
expect(removeResult).toEqual({ success: true });
// Verify it's gone
const listResult = await ipcMain.invokeHandler('project:list', {});
const data = (listResult as { data: unknown[] }).data;
expect(data).toHaveLength(0);
});
});
describe('project:updateSettings handler', () => {
it('should return error for non-existent project', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
const result = await ipcMain.invokeHandler(
'project:updateSettings',
{},
'nonexistent-id',
{ parallelEnabled: true }
);
expect(result).toEqual({
success: false,
error: 'Project not found'
});
});
it('should successfully update project settings', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
// Add a project first
const addResult = await ipcMain.invokeHandler('project:add', {}, TEST_PROJECT_PATH);
const projectId = (addResult as { data: { id: string } }).data.id;
// Update settings
const result = await ipcMain.invokeHandler(
'project:updateSettings',
{},
projectId,
{ parallelEnabled: true, maxWorkers: 4 }
);
expect(result).toEqual({ success: true });
});
});
describe('task:list handler', () => {
it('should return empty array for project with no specs', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
// Add a project first
const addResult = await ipcMain.invokeHandler('project:add', {}, TEST_PROJECT_PATH);
const projectId = (addResult as { data: { id: string } }).data.id;
const result = await ipcMain.invokeHandler('task:list', {}, projectId);
expect(result).toEqual({
success: true,
data: []
});
});
it('should return tasks when specs exist', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
// Add a project first
const addResult = await ipcMain.invokeHandler('project:add', {}, TEST_PROJECT_PATH);
const projectId = (addResult as { data: { id: string } }).data.id;
// Create a spec directory with implementation plan
const specDir = path.join(TEST_PROJECT_PATH, 'auto-claude', 'specs', '001-test-feature');
mkdirSync(specDir, { recursive: true });
writeFileSync(path.join(specDir, 'implementation_plan.json'), JSON.stringify({
feature: 'Test Feature',
workflow_type: 'feature',
services_involved: [],
phases: [{
phase: 1,
name: 'Test Phase',
type: 'implementation',
subtasks: [{ id: 'subtask-1', description: 'Test subtask', status: 'pending' }]
}],
final_acceptance: [],
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
spec_file: ''
}));
const result = await ipcMain.invokeHandler('task:list', {}, projectId);
expect(result).toHaveProperty('success', true);
const data = (result as { data: unknown[] }).data;
expect(data).toHaveLength(1);
});
});
describe('task:create handler', () => {
it('should return error for non-existent project', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
const result = await ipcMain.invokeHandler(
'task:create',
{},
'nonexistent-id',
'Test Task',
'Test description'
);
expect(result).toEqual({
success: false,
error: 'Project not found'
});
});
it('should create task and start spec creation', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
// Add a project first
const addResult = await ipcMain.invokeHandler('project:add', {}, TEST_PROJECT_PATH);
const projectId = (addResult as { data: { id: string } }).data.id;
const result = await ipcMain.invokeHandler(
'task:create',
{},
projectId,
'Test Task',
'Test description'
);
expect(result).toHaveProperty('success', true);
expect(mockAgentManager.startSpecCreation).toHaveBeenCalled();
});
});
describe('settings:get handler', () => {
it('should return default settings when no settings file exists', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
const result = await ipcMain.invokeHandler('settings:get', {});
expect(result).toHaveProperty('success', true);
const data = (result as { data: { theme: string } }).data;
expect(data).toHaveProperty('theme', 'system');
});
});
describe('settings:save handler', () => {
it('should save settings successfully', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
const result = await ipcMain.invokeHandler(
'settings:save',
{},
{ theme: 'dark', defaultModel: 'opus' }
);
expect(result).toEqual({ success: true });
// Verify settings were saved
const getResult = await ipcMain.invokeHandler('settings:get', {});
const data = (getResult as { data: { theme: string; defaultModel: string } }).data;
expect(data.theme).toBe('dark');
expect(data.defaultModel).toBe('opus');
});
it('should configure agent manager when paths change', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
await ipcMain.invokeHandler(
'settings:save',
{},
{ pythonPath: '/usr/bin/python3' }
);
expect(mockAgentManager.configure).toHaveBeenCalledWith('/usr/bin/python3', undefined);
});
});
describe('app:version handler', () => {
it('should return app version', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
const result = await ipcMain.invokeHandler('app:version', {});
expect(result).toBe('0.1.0');
});
});
describe('Agent Manager event forwarding', () => {
it('should forward log events to renderer', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
mockAgentManager.emit('log', 'task-1', 'Test log message');
expect(mockMainWindow.webContents.send).toHaveBeenCalledWith(
'task:log',
'task-1',
'Test log message'
);
});
it('should forward error events to renderer', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
mockAgentManager.emit('error', 'task-1', 'Test error message');
expect(mockMainWindow.webContents.send).toHaveBeenCalledWith(
'task:error',
'task-1',
'Test error message'
);
});
it('should forward exit events with status change', async () => {
const { setupIpcHandlers } = await import('../ipc-handlers');
setupIpcHandlers(mockAgentManager as never, mockTerminalManager as never, () => mockMainWindow as never);
mockAgentManager.emit('exit', 'task-1', 0);
expect(mockMainWindow.webContents.send).toHaveBeenCalledWith(
'task:statusChange',
'task-1',
'ai_review'
);
});
});
});
+1 -14
View File
@@ -6,9 +6,6 @@ import { AgentEvents } from './agent-events';
import { AgentProcessManager } from './agent-process';
import { AgentQueueManager } from './agent-queue';
import {
AgentManagerEvents,
ExecutionProgressData,
ProcessType,
SpecCreationMetadata,
TaskExecutionOptions,
IdeationConfig
@@ -44,8 +41,7 @@ export class AgentManager extends EventEmitter {
this.queueManager = new AgentQueueManager(this.state, this.events, this.processManager, this);
// Listen for auto-swap restart events
this.on('auto-swap-restart-task', (taskId: string, newProfileId: string) => {
console.log('[AgentManager] Auto-swap restart:', taskId, newProfileId);
this.on('auto-swap-restart-task', (taskId: string, _newProfileId: string) => {
this.restartTask(taskId);
});
@@ -64,14 +60,12 @@ export class AgentManager extends EventEmitter {
// If task completed successfully, always clean up
if (code === 0) {
this.taskExecutionContext.delete(taskId);
console.log('[AgentManager] Cleaned up context for completed task:', taskId);
return;
}
// If task failed and hit max retries, clean up
if (context.swapCount >= 2) {
this.taskExecutionContext.delete(taskId);
console.log('[AgentManager] Cleaned up context for max-retry task:', taskId);
}
// Otherwise keep context for potential restart
}, 1000); // Delay to allow restart logic to run first
@@ -142,21 +136,16 @@ export class AgentManager extends EventEmitter {
specId: string,
options: TaskExecutionOptions = {}
): void {
console.log('[AgentManager] startTaskExecution called for:', taskId, specId);
const autoBuildSource = this.processManager.getAutoBuildSourcePath();
if (!autoBuildSource) {
console.log('[AgentManager] ERROR: Auto-build source path not found');
this.emit('error', taskId, 'Auto-build source path not found. Please configure it in App Settings.');
return;
}
const runPath = path.join(autoBuildSource, 'run.py');
console.log('[AgentManager] runPath:', runPath);
if (!existsSync(runPath)) {
console.log('[AgentManager] ERROR: Run script not found at:', runPath);
this.emit('error', taskId, `Run script not found at: ${runPath}`);
return;
}
@@ -183,7 +172,6 @@ export class AgentManager extends EventEmitter {
// Store context for potential restart
this.storeTaskContext(taskId, projectPath, specId, options, false);
console.log('[AgentManager] Spawning process with args:', args);
this.processManager.spawnProcess(taskId, autoBuildSource, args, combinedEnv, 'task-execution');
}
@@ -329,7 +317,6 @@ export class AgentManager extends EventEmitter {
}
context.swapCount++;
console.log('[AgentManager] Restarting task:', taskId, 'swap count:', context.swapCount);
// Kill current process
this.killTask(taskId);
+2 -27
View File
@@ -1,4 +1,4 @@
import { spawn, ChildProcess } from 'child_process';
import { spawn } from 'child_process';
import path from 'path';
import { existsSync, readFileSync } from 'fs';
import { app } from 'electron';
@@ -100,14 +100,11 @@ export class AgentProcessManager {
loadAutoBuildEnv(): Record<string, string> {
const autoBuildSource = this.getAutoBuildSourcePath();
if (!autoBuildSource) {
console.log('[loadAutoBuildEnv] No auto-build source path found');
return {};
}
const envPath = path.join(autoBuildSource, '.env');
console.log('[loadAutoBuildEnv] Looking for .env at:', envPath);
if (!existsSync(envPath)) {
console.log('[loadAutoBuildEnv] .env file does not exist');
return {};
}
@@ -161,11 +158,6 @@ export class AgentProcessManager {
// Generate unique spawn ID for this process instance
const spawnId = this.state.generateSpawnId();
console.log('[spawnProcess] Spawning with pythonPath:', this.pythonPath);
console.log('[spawnProcess] cwd:', cwd);
console.log('[spawnProcess] processType:', processType);
console.log('[spawnProcess] spawnId:', spawnId);
// Get active Claude profile environment (CLAUDE_CONFIG_DIR if not default)
const profileEnv = getProfileEnv();
@@ -181,8 +173,6 @@ export class AgentProcessManager {
}
});
console.log('[spawnProcess] Process spawned, pid:', childProcess.pid);
this.state.addProcess(taskId, {
taskId,
process: childProcess,
@@ -245,7 +235,6 @@ export class AgentProcessManager {
// Handle stdout - explicitly decode as UTF-8 for cross-platform Unicode support
childProcess.stdout?.on('data', (data: Buffer) => {
const log = data.toString('utf8');
console.log('[spawnProcess] stdout:', log.substring(0, 200));
this.emitter.emit('log', taskId, log);
processLog(log);
});
@@ -253,7 +242,6 @@ export class AgentProcessManager {
// Handle stderr - explicitly decode as UTF-8 for cross-platform Unicode support
childProcess.stderr?.on('data', (data: Buffer) => {
const log = data.toString('utf8');
console.log('[spawnProcess] stderr:', log.substring(0, 200));
// Some Python output goes to stderr (like progress bars)
// so we treat it as log, not error
this.emitter.emit('log', taskId, log);
@@ -262,13 +250,11 @@ export class AgentProcessManager {
// Handle process exit
childProcess.on('exit', (code: number | null) => {
console.log('[spawnProcess] Process exited with code:', code, 'spawnId:', spawnId);
this.state.deleteProcess(taskId);
// Check if this specific spawn was killed (vs exited naturally)
// If killed, don't emit exit event to prevent race condition with new process
if (this.state.wasSpawnKilled(spawnId)) {
console.log('[spawnProcess] Process was killed, skipping exit event for spawnId:', spawnId);
this.state.clearKilledSpawn(spawnId);
return;
}
@@ -277,26 +263,15 @@ export class AgentProcessManager {
if (code !== 0) {
const rateLimitDetection = detectRateLimit(allOutput);
if (rateLimitDetection.isRateLimited) {
console.log('[spawnProcess] Rate limit detected in task output:', {
taskId,
resetTime: rateLimitDetection.resetTime,
limitType: rateLimitDetection.limitType,
suggestedProfile: rateLimitDetection.suggestedProfile?.name
});
// Check if auto-swap is enabled
const profileManager = getClaudeProfileManager();
const autoSwitchSettings = profileManager.getAutoSwitchSettings();
if (autoSwitchSettings.enabled && autoSwitchSettings.autoSwitchOnRateLimit) {
console.log('[spawnProcess] Reactive auto-swap enabled');
const currentProfileId = rateLimitDetection.profileId;
const bestProfile = profileManager.getBestAvailableProfile(currentProfileId);
if (bestProfile) {
console.log('[spawnProcess] Reactive swap to:', bestProfile.name);
// Switch active profile
profileManager.setActiveProfile(bestProfile.id);
@@ -342,7 +317,7 @@ export class AgentProcessManager {
// Handle process error
childProcess.on('error', (err: Error) => {
console.log('[spawnProcess] Process error:', err.message);
console.error('[AgentProcess] Process error:', err.message);
this.state.deleteProcess(taskId);
this.emitter.emit('execution-progress', taskId, {
@@ -182,15 +182,11 @@ export class AgentQueueManager {
for (const line of lines) {
const trimmed = line.trim();
if (trimmed.length > 0) {
console.log('[Ideation]', trimmed);
this.emitter.emit('ideation-log', projectId, trimmed);
}
}
};
console.log('[Ideation] Starting ideation process with args:', args);
console.log('[Ideation] CWD:', cwd);
// Track completed types for progress calculation
const completedTypes = new Set<string>();
const totalTypes = 7; // Default all types
@@ -209,7 +205,6 @@ export class AgentQueueManager {
if (typeCompleteMatch) {
const [, ideationType, ideasCount] = typeCompleteMatch;
completedTypes.add(ideationType);
console.log(`[Ideation] Type complete: ${ideationType} with ${ideasCount} ideas`);
// Emit event for UI to load this type's ideas immediately
this.emitter.emit('ideation-type-complete', projectId, ideationType, parseInt(ideasCount, 10));
@@ -219,7 +214,6 @@ export class AgentQueueManager {
if (typeFailedMatch) {
const [, ideationType] = typeFailedMatch;
completedTypes.add(ideationType);
console.log(`[Ideation] Type failed: ${ideationType}`);
this.emitter.emit('ideation-type-failed', projectId, ideationType);
}
@@ -260,8 +254,6 @@ export class AgentQueueManager {
// Handle process exit
childProcess.on('exit', (code: number | null) => {
console.log('[Ideation] Process exited with code:', code);
// Get the stored project path before deleting from map
const processInfo = this.state.getProcess(projectId);
const storedProjectPath = processInfo?.projectPath;
@@ -297,7 +289,6 @@ export class AgentQueueManager {
if (existsSync(ideationFilePath)) {
const content = readFileSync(ideationFilePath, 'utf-8');
const session = JSON.parse(content);
console.log('[Ideation] Emitting ideation-complete with session data');
this.emitter.emit('ideation-complete', projectId, session);
} else {
console.warn('[Ideation] ideation.json not found at:', ideationFilePath);
@@ -346,10 +337,6 @@ export class AgentQueueManager {
// Get Python path from process manager (uses venv if configured)
const pythonPath = this.processManager.getPythonPath();
console.log('[Roadmap] Starting roadmap process with args:', args);
console.log('[Roadmap] CWD:', cwd);
console.log('[Roadmap] Python path:', pythonPath);
const childProcess = spawn(pythonPath, args, {
cwd,
env: {
@@ -382,7 +369,6 @@ export class AgentQueueManager {
for (const line of lines) {
const trimmed = line.trim();
if (trimmed.length > 0) {
console.log('[Roadmap]', trimmed);
this.emitter.emit('roadmap-log', projectId, trimmed);
}
}
@@ -426,8 +412,6 @@ export class AgentQueueManager {
// Handle process exit
childProcess.on('exit', (code: number | null) => {
console.log('[Roadmap] Process exited with code:', code);
// Get the stored project path before deleting from map
const processInfo = this.state.getProcess(projectId);
const storedProjectPath = processInfo?.projectPath;
@@ -445,7 +429,6 @@ export class AgentQueueManager {
}
if (code === 0) {
console.log('[Roadmap] Roadmap generation completed successfully');
this.emitter.emit('roadmap-progress', projectId, {
phase: 'complete',
progress: 100,
@@ -464,7 +447,6 @@ export class AgentQueueManager {
if (existsSync(roadmapFilePath)) {
const content = readFileSync(roadmapFilePath, 'utf-8');
const roadmap = JSON.parse(content);
console.log('[Roadmap] Emitting roadmap-complete with roadmap data');
this.emitter.emit('roadmap-complete', projectId, roadmap);
} else {
console.warn('[Roadmap] roadmap.json not found at:', roadmapFilePath);
@@ -474,7 +456,6 @@ export class AgentQueueManager {
}
}
} else {
console.error('[Roadmap] Roadmap generation failed with exit code:', code);
this.emitter.emit('roadmap-error', projectId, `Roadmap generation failed with exit code ${code}`);
}
});
+24 -24
View File
@@ -33,10 +33,10 @@ autoUpdater.autoInstallOnAppQuit = true; // Automatically install on app quit
// Enable more verbose logging in debug mode
if (DEBUG_UPDATER) {
autoUpdater.logger = {
info: (msg: string) => console.log('[app-updater:debug]', msg),
info: (msg: string) => console.warn('[app-updater:debug]', msg),
warn: (msg: string) => console.warn('[app-updater:debug]', msg),
error: (msg: string) => console.error('[app-updater:debug]', msg),
debug: (msg: string) => console.log('[app-updater:debug]', msg)
debug: (msg: string) => console.warn('[app-updater:debug]', msg)
};
}
@@ -54,13 +54,13 @@ export function initializeAppUpdater(window: BrowserWindow): void {
mainWindow = window;
// Log updater configuration
console.log('[app-updater] ========================================');
console.log('[app-updater] Initializing app auto-updater');
console.log('[app-updater] App packaged:', app.isPackaged);
console.log('[app-updater] Current version:', autoUpdater.currentVersion.version);
console.log('[app-updater] Auto-download enabled:', autoUpdater.autoDownload);
console.log('[app-updater] Debug mode:', DEBUG_UPDATER);
console.log('[app-updater] ========================================');
console.warn('[app-updater] ========================================');
console.warn('[app-updater] Initializing app auto-updater');
console.warn('[app-updater] App packaged:', app.isPackaged);
console.warn('[app-updater] Current version:', autoUpdater.currentVersion.version);
console.warn('[app-updater] Auto-download enabled:', autoUpdater.autoDownload);
console.warn('[app-updater] Debug mode:', DEBUG_UPDATER);
console.warn('[app-updater] ========================================');
// ============================================
// Event Handlers
@@ -68,7 +68,7 @@ export function initializeAppUpdater(window: BrowserWindow): void {
// Update available - new version found
autoUpdater.on('update-available', (info) => {
console.log('[app-updater] Update available:', info.version);
console.warn('[app-updater] Update available:', info.version);
if (mainWindow) {
mainWindow.webContents.send(IPC_CHANNELS.APP_UPDATE_AVAILABLE, {
version: info.version,
@@ -80,7 +80,7 @@ export function initializeAppUpdater(window: BrowserWindow): void {
// Update downloaded - ready to install
autoUpdater.on('update-downloaded', (info) => {
console.log('[app-updater] Update downloaded:', info.version);
console.warn('[app-updater] Update downloaded:', info.version);
if (mainWindow) {
mainWindow.webContents.send(IPC_CHANNELS.APP_UPDATE_DOWNLOADED, {
version: info.version,
@@ -92,7 +92,7 @@ export function initializeAppUpdater(window: BrowserWindow): void {
// Download progress
autoUpdater.on('download-progress', (progress) => {
console.log(`[app-updater] Download progress: ${progress.percent.toFixed(2)}%`);
console.warn(`[app-updater] Download progress: ${progress.percent.toFixed(2)}%`);
if (mainWindow) {
mainWindow.webContents.send(IPC_CHANNELS.APP_UPDATE_PROGRESS, {
percent: progress.percent,
@@ -116,16 +116,16 @@ export function initializeAppUpdater(window: BrowserWindow): void {
// No update available
autoUpdater.on('update-not-available', (info) => {
console.log('[app-updater] No updates available - you are on the latest version');
console.log('[app-updater] Current version:', info.version);
console.warn('[app-updater] No updates available - you are on the latest version');
console.warn('[app-updater] Current version:', info.version);
if (DEBUG_UPDATER) {
console.log('[app-updater:debug] Full info:', JSON.stringify(info, null, 2));
console.warn('[app-updater:debug] Full info:', JSON.stringify(info, null, 2));
}
});
// Checking for updates
autoUpdater.on('checking-for-update', () => {
console.log('[app-updater] 🔍 Checking for updates...');
console.warn('[app-updater] Checking for updates...');
});
// ============================================
@@ -134,10 +134,10 @@ export function initializeAppUpdater(window: BrowserWindow): void {
// Check for updates 3 seconds after launch
const INITIAL_DELAY = 3000;
console.log(`[app-updater] Will check for updates in ${INITIAL_DELAY / 1000} seconds...`);
console.warn(`[app-updater] Will check for updates in ${INITIAL_DELAY / 1000} seconds...`);
setTimeout(() => {
console.log('[app-updater] 🚀 Performing initial update check');
console.warn('[app-updater] Performing initial update check');
autoUpdater.checkForUpdates().catch((error) => {
console.error('[app-updater] ❌ Initial update check failed:', error.message);
if (DEBUG_UPDATER) {
@@ -148,10 +148,10 @@ export function initializeAppUpdater(window: BrowserWindow): void {
// Check for updates every 4 hours
const FOUR_HOURS = 4 * 60 * 60 * 1000;
console.log(`[app-updater] Periodic checks scheduled every ${FOUR_HOURS / 1000 / 60 / 60} hours`);
console.warn(`[app-updater] Periodic checks scheduled every ${FOUR_HOURS / 1000 / 60 / 60} hours`);
setInterval(() => {
console.log('[app-updater] 🔄 Performing periodic update check');
console.warn('[app-updater] Performing periodic update check');
autoUpdater.checkForUpdates().catch((error) => {
console.error('[app-updater] ❌ Periodic update check failed:', error.message);
if (DEBUG_UPDATER) {
@@ -160,7 +160,7 @@ export function initializeAppUpdater(window: BrowserWindow): void {
});
}, FOUR_HOURS);
console.log('[app-updater] Auto-updater initialized successfully');
console.warn('[app-updater] Auto-updater initialized successfully');
}
/**
@@ -169,7 +169,7 @@ export function initializeAppUpdater(window: BrowserWindow): void {
*/
export async function checkForUpdates(): Promise<AppUpdateInfo | null> {
try {
console.log('[app-updater] Manual update check requested');
console.warn('[app-updater] Manual update check requested');
const result = await autoUpdater.checkForUpdates();
if (!result) {
@@ -199,7 +199,7 @@ export async function checkForUpdates(): Promise<AppUpdateInfo | null> {
*/
export async function downloadUpdate(): Promise<void> {
try {
console.log('[app-updater] Manual update download requested');
console.warn('[app-updater] Manual update download requested');
await autoUpdater.downloadUpdate();
} catch (error) {
console.error('[app-updater] Manual update download failed:', error);
@@ -212,7 +212,7 @@ export async function downloadUpdate(): Promise<void> {
* Called from IPC handler when user confirms installation
*/
export function quitAndInstall(): void {
console.log('[app-updater] Quitting and installing update');
console.warn('[app-updater] Quitting and installing update');
autoUpdater.quitAndInstall(false, true);
}
@@ -119,7 +119,7 @@ export class ChangelogService extends EventEmitter {
*/
private debug(...args: unknown[]): void {
if (this.isDebugEnabled()) {
console.log('[ChangelogService]', ...args);
console.warn('[ChangelogService]', ...args);
}
}
@@ -34,7 +34,7 @@ export class ChangelogGenerator extends EventEmitter {
private debug(...args: unknown[]): void {
if (this.debugEnabled) {
console.log('[ChangelogGenerator]', ...args);
console.warn('[ChangelogGenerator]', ...args);
}
}
@@ -13,7 +13,7 @@ import { parseGitLogOutput } from './parser';
*/
function debug(enabled: boolean, ...args: unknown[]): void {
if (enabled) {
console.log('[GitIntegration]', ...args);
console.warn('[GitIntegration]', ...args);
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ export function extractSpecOverview(spec: string): string {
// Handle both Unix (\n) and Windows (\r\n) line endings
const lines = spec.split(/\r?\n/);
let inOverview = false;
let overview: string[] = [];
const overview: string[] = [];
for (const line of lines) {
// Start capturing at Overview heading
@@ -28,7 +28,7 @@ export class VersionSuggester {
private debug(...args: unknown[]): void {
if (this.debugEnabled) {
console.log('[VersionSuggester]', ...args);
console.warn('[VersionSuggester]', ...args);
}
}
@@ -51,7 +51,7 @@ export class VersionSuggester {
// Build environment
const spawnEnv = this.buildSpawnEnvironment();
return new Promise((resolve, reject) => {
return new Promise((resolve, _reject) => {
const childProcess = spawn(this.pythonPath, ['-c', script], {
cwd: this.autoBuildSourcePath,
env: spawnEnv
@@ -240,7 +240,7 @@ export class ClaudeProfileManager {
profile.name = newName.trim();
this.save();
console.log('[ClaudeProfileManager] Renamed profile:', profileId, 'to:', newName);
console.warn('[ClaudeProfileManager] Renamed profile:', profileId, 'to:', newName);
return true;
}
@@ -317,7 +317,7 @@ export class ClaudeProfileManager {
this.save();
const isEncrypted = profile.oauthToken.startsWith('enc:');
console.log('[ClaudeProfileManager] Set OAuth token for profile:', profile.name, {
console.warn('[ClaudeProfileManager] Set OAuth token for profile:', profile.name, {
email: email || '(not captured)',
encrypted: isEncrypted,
tokenLength: token.length
@@ -350,14 +350,14 @@ export class ClaudeProfileManager {
const decryptedToken = decryptToken(profile.oauthToken);
if (decryptedToken) {
env.CLAUDE_CODE_OAUTH_TOKEN = decryptedToken;
console.log('[ClaudeProfileManager] Using OAuth token for profile:', profile.name);
console.warn('[ClaudeProfileManager] Using OAuth token for profile:', profile.name);
} else {
console.warn('[ClaudeProfileManager] Failed to decrypt token for profile:', profile.name);
}
} else if (profile?.configDir && !profile.isDefault) {
// Fallback to configDir for backward compatibility
env.CLAUDE_CONFIG_DIR = profile.configDir;
console.log('[ClaudeProfileManager] Using configDir for profile:', profile.name);
console.warn('[ClaudeProfileManager] Using configDir for profile:', profile.name);
}
return env;
@@ -376,7 +376,7 @@ export class ClaudeProfileManager {
profile.usage = usage;
this.save();
console.log('[ClaudeProfileManager] Updated usage for', profile.name, ':', usage);
console.warn('[ClaudeProfileManager] Updated usage for', profile.name, ':', usage);
return usage;
}
@@ -392,7 +392,7 @@ export class ClaudeProfileManager {
const event = recordRateLimitEventImpl(profile, resetTimeStr);
this.save();
console.log('[ClaudeProfileManager] Recorded rate limit event for', profile.name, ':', event);
console.warn('[ClaudeProfileManager] Recorded rate limit event for', profile.name, ':', event);
return event;
}
@@ -86,12 +86,12 @@ export function getBestAvailableProfile(
// Return the best candidate if it has a positive score
const best = scoredProfiles[0];
if (best && best.score > 0) {
console.log('[ProfileScorer] Best available profile:', best.profile.name, 'score:', best.score);
console.warn('[ProfileScorer] Best available profile:', best.profile.name, 'score:', best.score);
return best.profile;
}
// All profiles are rate-limited or have issues
console.log('[ProfileScorer] No good profile available, all are rate-limited or have issues');
console.warn('[ProfileScorer] No good profile available, all are rate-limited or have issues');
return null;
}
@@ -143,7 +143,7 @@ export function shouldProactivelySwitch(
* Get profiles sorted by availability (best first)
*/
export function getProfilesSortedByAvailability(profiles: ClaudeProfile[]): ClaudeProfile[] {
const now = new Date();
const _now = new Date();
return [...profiles].sort((a, b) => {
// Not rate-limited profiles first
@@ -117,7 +117,7 @@ export function hasValidToken(profile: ClaudeProfile): boolean {
const oneYearAgo = new Date();
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
if (new Date(profile.tokenCreatedAt) < oneYearAgo) {
console.log('[ProfileUtils] Token expired for profile:', profile.name);
console.warn('[ProfileUtils] Token expired for profile:', profile.name);
return false;
}
}
@@ -22,7 +22,7 @@ export class UsageMonitor extends EventEmitter {
private constructor() {
super();
console.log('[UsageMonitor] Initialized');
console.warn('[UsageMonitor] Initialized');
}
static getInstance(): UsageMonitor {
@@ -40,17 +40,17 @@ export class UsageMonitor extends EventEmitter {
const settings = profileManager.getAutoSwitchSettings();
if (!settings.enabled || !settings.proactiveSwapEnabled) {
console.log('[UsageMonitor] Proactive monitoring disabled');
console.warn('[UsageMonitor] Proactive monitoring disabled');
return;
}
if (this.intervalId) {
console.log('[UsageMonitor] Already running');
console.warn('[UsageMonitor] Already running');
return;
}
const interval = settings.usageCheckInterval || 30000;
console.log('[UsageMonitor] Starting with interval:', interval, 'ms');
console.warn('[UsageMonitor] Starting with interval:', interval, 'ms');
// Check immediately
this.checkUsageAndSwap();
@@ -68,7 +68,7 @@ export class UsageMonitor extends EventEmitter {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
console.log('[UsageMonitor] Stopped');
console.warn('[UsageMonitor] Stopped');
}
}
@@ -94,14 +94,14 @@ export class UsageMonitor extends EventEmitter {
const activeProfile = profileManager.getActiveProfile();
if (!activeProfile) {
console.log('[UsageMonitor] No active profile');
console.warn('[UsageMonitor] No active profile');
return;
}
// Fetch current usage (hybrid approach)
const usage = await this.fetchUsage(activeProfile.id, activeProfile.oauthToken);
if (!usage) {
console.log('[UsageMonitor] Failed to fetch usage');
console.warn('[UsageMonitor] Failed to fetch usage');
return;
}
@@ -116,7 +116,7 @@ export class UsageMonitor extends EventEmitter {
const weeklyExceeded = usage.weeklyPercent >= settings.weeklyThreshold;
if (sessionExceeded || weeklyExceeded) {
console.log('[UsageMonitor] Threshold exceeded:', {
console.warn('[UsageMonitor] Threshold exceeded:', {
sessionPercent: usage.sessionPercent,
sessionThreshold: settings.sessionThreshold,
weeklyPercent: usage.weeklyPercent,
@@ -154,12 +154,12 @@ export class UsageMonitor extends EventEmitter {
if (this.useApiMethod && oauthToken) {
const apiUsage = await this.fetchUsageViaAPI(oauthToken, profileId, profile.name);
if (apiUsage) {
console.log('[UsageMonitor] Successfully fetched via API');
console.warn('[UsageMonitor] Successfully fetched via API');
return apiUsage;
}
// API failed - switch to CLI method for future calls
console.log('[UsageMonitor] API method failed, falling back to CLI');
console.warn('[UsageMonitor] API method failed, falling back to CLI');
this.useApiMethod = false;
}
@@ -191,7 +191,12 @@ export class UsageMonitor extends EventEmitter {
return null;
}
const data: any = await response.json();
const data = await response.json() as {
five_hour_utilization?: number;
seven_day_utilization?: number;
five_hour_reset_at?: string;
seven_day_reset_at?: string;
};
// Expected response format:
// {
@@ -232,7 +237,7 @@ export class UsageMonitor extends EventEmitter {
// CLI-based usage fetching is not implemented yet.
// The API method should handle most cases. If we need CLI fallback,
// we would need to spawn a Claude process with /usage command and parse the output.
console.log('[UsageMonitor] CLI fallback not implemented, API method should be used');
console.warn('[UsageMonitor] CLI fallback not implemented, API method should be used');
return null;
}
@@ -256,7 +261,7 @@ export class UsageMonitor extends EventEmitter {
const diffDays = Math.floor(diffHours / 24);
const remainingHours = diffHours % 24;
return `${diffDays}d ${remainingHours}h`;
} catch (error) {
} catch (_error) {
return isoTimestamp;
}
}
@@ -272,7 +277,7 @@ export class UsageMonitor extends EventEmitter {
const bestProfile = profileManager.getBestAvailableProfile(currentProfileId);
if (!bestProfile) {
console.log('[UsageMonitor] No alternative profile for proactive swap');
console.warn('[UsageMonitor] No alternative profile for proactive swap');
this.emit('proactive-swap-failed', {
reason: 'no_alternative',
currentProfile: currentProfileId
@@ -280,7 +285,7 @@ export class UsageMonitor extends EventEmitter {
return;
}
console.log('[UsageMonitor] Proactive swap:', {
console.warn('[UsageMonitor] Proactive swap:', {
from: currentProfileId,
to: bestProfile.id,
reason: limitType
+4 -4
View File
@@ -158,7 +158,7 @@ export async function checkFalkorDBStatus(port: number = FALKORDB_DEFAULT_PORT):
/**
* Check if FalkorDB is responding to connections
*/
async function checkFalkorDBHealth(port: number): Promise<boolean> {
async function checkFalkorDBHealth(_port: number): Promise<boolean> {
try {
// Try to ping FalkorDB using redis-cli (FalkorDB uses Redis protocol)
// Since we may not have redis-cli, we'll check if the port is listening
@@ -482,10 +482,10 @@ export async function validateOpenAIApiKey(
timeout: 15000,
};
const req = https.request(options, (res: any) => {
const req = https.request(options, (res: { statusCode: number; on: (event: string, callback: (chunk: Buffer) => void) => void }) => {
let data = '';
res.on('data', (chunk: any) => {
res.on('data', (chunk: Buffer) => {
data += chunk;
});
@@ -533,7 +533,7 @@ export async function validateOpenAIApiKey(
});
});
req.on('error', (error: any) => {
req.on('error', (error: Error) => {
resolve({
success: false,
message: `Connection error: ${error.message}`,
+10 -10
View File
@@ -137,23 +137,23 @@ app.whenReady().then(() => {
// Start the usage monitor
const usageMonitor = getUsageMonitor();
usageMonitor.start();
console.log('[main] Usage monitor initialized and started');
console.warn('[main] Usage monitor initialized and started');
// Initialize app auto-updater (only in production, or when DEBUG_UPDATER is set)
const forceUpdater = process.env.DEBUG_UPDATER === 'true';
if (app.isPackaged || forceUpdater) {
initializeAppUpdater(mainWindow);
console.log('[main] App auto-updater initialized');
console.warn('[main] App auto-updater initialized');
if (forceUpdater && !app.isPackaged) {
console.log('[main] ⚠️ Updater forced in dev mode via DEBUG_UPDATER=true');
console.log('[main] ⚠️ Note: Updates won\'t actually work in dev mode');
console.warn('[main] Updater forced in dev mode via DEBUG_UPDATER=true');
console.warn('[main] Note: Updates won\'t actually work in dev mode');
}
} else {
console.log('[main] ========================================');
console.log('[main] App auto-updater DISABLED (development mode)');
console.log('[main] To test updater logging, set DEBUG_UPDATER=true');
console.log('[main] Note: Actual updates only work in packaged builds');
console.log('[main] ========================================');
console.warn('[main] ========================================');
console.warn('[main] App auto-updater DISABLED (development mode)');
console.warn('[main] To test updater logging, set DEBUG_UPDATER=true');
console.warn('[main] Note: Actual updates only work in packaged builds');
console.warn('[main] ========================================');
}
}
@@ -177,7 +177,7 @@ app.on('before-quit', async () => {
// Stop usage monitor
const usageMonitor = getUsageMonitor();
usageMonitor.stop();
console.log('[main] Usage monitor stopped');
console.warn('[main] Usage monitor stopped');
// Kill all running agent processes
if (agentManager) {
+1 -4
View File
@@ -2,10 +2,7 @@ import { EventEmitter } from 'events';
import type {
InsightsSession,
InsightsSessionSummary,
InsightsChatMessage,
InsightsChatStatus,
InsightsStreamChunk,
InsightsToolUsage
InsightsChatMessage
} from '../shared/types';
import { InsightsConfig } from './insights/config';
import { InsightsPaths } from './insights/paths';
@@ -251,7 +251,7 @@ export class InsightsExecutor extends EventEmitter {
private handleRateLimit(projectId: string, output: string): void {
const rateLimitDetection = detectRateLimit(output);
if (rateLimitDetection.isRateLimited) {
console.log('[Insights] Rate limit detected:', {
console.warn('[Insights] Rate limit detected:', {
projectId,
resetTime: rateLimitDetection.resetTime,
limitType: rateLimitDetection.limitType,
@@ -1,16 +1,13 @@
import { ipcMain } from 'electron';
import type { BrowserWindow } from 'electron';
import path from 'path';
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { IPC_CHANNELS, getSpecsDir, AUTO_BUILD_PATHS } from '../../shared/constants';
import type {
IPCResult,
SDKRateLimitInfo,
Task,
TaskStatus,
Project,
ImplementationPlan,
ExecutionProgress
ImplementationPlan
} from '../../shared/types';
import { AgentManager } from '../agent';
import type { ProcessType, ExecutionProgressData } from '../agent';
@@ -82,7 +79,7 @@ export function registerAgenteventsHandlers(
} else if (processType === 'spec-creation') {
// Pure spec creation (shouldn't happen with current flow, but handle it)
// Stay in backlog/planning
console.log(`[Task ${taskId}] Spec creation completed with code ${code}`);
console.warn(`[Task ${taskId}] Spec creation completed with code ${code}`);
return;
} else {
// Unknown process type
@@ -130,7 +127,7 @@ export function registerAgenteventsHandlers(
plan.planStatus = 'review';
plan.updated_at = new Date().toISOString();
writeFileSync(planPath, JSON.stringify(plan, null, 2));
console.log(`[Task ${taskId}] Persisted status '${newStatus}' to implementation_plan.json`);
console.warn(`[Task ${taskId}] Persisted status '${newStatus}' to implementation_plan.json`);
}
}
}
@@ -141,7 +138,7 @@ export function registerAgenteventsHandlers(
// Send notifications based on task completion status
if (task && project) {
const taskTitle = task.title || task.specId;
if (code === 0) {
// Task completed successfully - ready for review
notificationService.notifyReviewNeeded(taskTitle, project.id, taskId);
@@ -19,7 +19,7 @@ import {
* Register all app-update-related IPC handlers
*/
export function registerAppUpdateHandlers(): void {
console.log('[IPC] Registering app update handlers');
console.warn('[IPC] Registering app update handlers');
// ============================================
// App Update Operations
@@ -102,5 +102,5 @@ export function registerAppUpdateHandlers(): void {
}
);
console.log('[IPC] App update handlers registered successfully');
console.warn('[IPC] App update handlers registered successfully');
}
@@ -1,8 +1,7 @@
import { ipcMain } from 'electron';
import type { BrowserWindow } from 'electron';
import path from 'path';
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
import { execSync } from 'child_process';
import { existsSync, mkdirSync, writeFileSync } from 'fs';
import { IPC_CHANNELS, getSpecsDir } from '../../shared/constants';
import type {
IPCResult,
@@ -155,7 +155,7 @@ export function searchFileBasedMemories(
* Register memory data handlers
*/
export function registerMemoryDataHandlers(
getMainWindow: () => BrowserWindow | null
_getMainWindow: () => BrowserWindow | null
): void {
// Get all memories
ipcMain.handle(
@@ -109,7 +109,7 @@ export function buildMemoryStatus(
* Register memory status handlers
*/
export function registerMemoryStatusHandlers(
getMainWindow: () => BrowserWindow | null
_getMainWindow: () => BrowserWindow | null
): void {
ipcMain.handle(
IPC_CHANNELS.CONTEXT_MEMORY_STATUS,
@@ -13,10 +13,7 @@ import type {
import { projectStore } from '../../project-store';
import { getFalkorDBService } from '../../falkordb-service';
import {
getAutoBuildSourcePath,
loadProjectEnvVars,
isGraphitiEnabled,
getGraphitiConnectionDetails
getAutoBuildSourcePath
} from './utils';
import {
loadGraphitiStateFromSpecs,
@@ -83,7 +80,7 @@ async function loadRecentMemories(
* Register project context handlers
*/
export function registerProjectContextHandlers(
getMainWindow: () => BrowserWindow | null
_getMainWindow: () => BrowserWindow | null
): void {
// Get full project context
ipcMain.handle(
@@ -5,7 +5,7 @@ import type { IPCResult, ProjectEnvConfig, ClaudeAuthResult, AppSettings } from
import path from 'path';
import { app } from 'electron';
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { execSync, spawn } from 'child_process';
import { spawn } from 'child_process';
import { projectStore } from '../project-store';
import { parseEnvFile } from './utils';
@@ -14,7 +14,7 @@ import { parseEnvFile } from './utils';
* Register all env-related IPC handlers
*/
export function registerEnvHandlers(
getMainWindow: () => BrowserWindow | null
_getMainWindow: () => BrowserWindow | null
): void {
// ============================================
// Environment Configuration Operations
@@ -85,7 +85,7 @@ export function registerEnvHandlers(
}
// Generate content with sections
let content = `# Auto Claude Framework Environment Variables
const content = `# Auto Claude Framework Environment Variables
# Managed by Auto Claude UI
# Claude Code OAuth Token (REQUIRED)
@@ -304,15 +304,15 @@ ${existingVars['GRAPHITI_DATABASE'] ? `GRAPHITI_DATABASE=${existingVars['GRAPHIT
shell: true
});
let stdout = '';
let stderr = '';
let _stdout = '';
let _stderr = '';
proc.stdout?.on('data', (data: Buffer) => {
stdout += data.toString();
_stdout += data.toString();
});
proc.stderr?.on('data', (data: Buffer) => {
stderr += data.toString();
_stderr += data.toString();
});
proc.on('close', (code: number | null) => {
@@ -14,9 +14,9 @@ const DEBUG = process.env.DEBUG === 'true' || process.env.NODE_ENV === 'developm
function debugLog(message: string, data?: unknown): void {
if (DEBUG) {
if (data !== undefined) {
console.log(`[GitHub OAuth] ${message}`, data);
console.warn(`[GitHub OAuth] ${message}`, data);
} else {
console.log(`[GitHub OAuth] ${message}`);
console.warn(`[GitHub OAuth] ${message}`);
}
}
}
@@ -237,7 +237,7 @@ export function registerSuggestVersion(): void {
commitCount: commits.length
}
};
} catch (error) {
} catch (_error) {
// Fallback to patch bump on error
const currentVersion = getCurrentVersion(project.path);
const [major, minor, patch] = currentVersion.split('.').map(Number);
@@ -35,7 +35,7 @@ export async function getIdeationSession(
try {
// Transform snake_case to camelCase for frontend
const enabledTypes = (rawIdeation.config?.enabled_types || rawIdeation.config?.enabledTypes || []) as any[];
const enabledTypes = (rawIdeation.config?.enabled_types || rawIdeation.config?.enabledTypes || []) as unknown[];
const session: IdeationSession = {
id: rawIdeation.id || `ideation-${Date.now()}`,
@@ -160,7 +160,7 @@ function buildTaskMetadata(idea: RawIdea): TaskMetadata {
function createSpecFiles(
specDir: string,
idea: RawIdea,
taskDescription: string
_taskDescription: string
): void {
// Create the spec directory
mkdirSync(specDir, { recursive: true });
@@ -98,7 +98,7 @@ export function setupIpcHandlers(
// App auto-update handlers
registerAppUpdateHandlers();
console.log('[IPC] All handler modules registered successfully');
console.warn('[IPC] All handler modules registered successfully');
}
// Re-export all individual registration functions for potential custom usage
@@ -1,10 +1,9 @@
import { ipcMain } from 'electron';
import type { BrowserWindow } from 'electron';
import { IPC_CHANNELS, getSpecsDir, AUTO_BUILD_PATHS } from '../../shared/constants';
import type { IPCResult, LinearIssue, LinearTeam, LinearProject, LinearImportResult, LinearSyncStatus, Project, Task, TaskMetadata } from '../../shared/types';
import type { IPCResult, LinearIssue, LinearTeam, LinearProject, LinearImportResult, LinearSyncStatus, Project, TaskMetadata } from '../../shared/types';
import path from 'path';
import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync } from 'fs';
import { spawn } from 'child_process';
import { projectStore } from '../project-store';
import { parseEnvFile } from './utils';
@@ -16,7 +15,7 @@ import { AgentManager } from '../agent';
*/
export function registerLinearHandlers(
agentManager: AgentManager,
getMainWindow: () => BrowserWindow | null
_getMainWindow: () => BrowserWindow | null
): void {
// ============================================
// Linear Integration Operations
@@ -115,7 +114,8 @@ export function registerLinearHandlers(
if (data.teams.nodes.length > 0) {
teamName = data.teams.nodes[0].name;
const countQuery = `
// Note: These queries are kept as documentation for future API reference
const _countQuery = `
query($teamId: String!) {
team(id: $teamId) {
issues {
@@ -125,7 +125,7 @@ export function registerLinearHandlers(
}
`;
// Get approximate count
const issuesQuery = `
const _issuesQuery = `
query($teamId: String!) {
issues(filter: { team: { id: { eq: $teamId } } }, first: 0) {
pageInfo {
@@ -134,6 +134,8 @@ export function registerLinearHandlers(
}
}
`;
void _countQuery;
void _issuesQuery;
// Simple count estimation - get first 250 issues
const countData = await linearGraphQL(apiKey, `
@@ -140,12 +140,12 @@ const detectAutoBuildSourcePath = (): string | null => {
const debug = process.env.AUTO_CLAUDE_DEBUG === '1' || process.env.AUTO_CLAUDE_DEBUG === 'true';
if (debug) {
console.log('[project-handlers:detectAutoBuildSourcePath] Platform:', process.platform);
console.log('[project-handlers:detectAutoBuildSourcePath] Is dev:', is.dev);
console.log('[project-handlers:detectAutoBuildSourcePath] __dirname:', __dirname);
console.log('[project-handlers:detectAutoBuildSourcePath] app.getAppPath():', app.getAppPath());
console.log('[project-handlers:detectAutoBuildSourcePath] process.cwd():', process.cwd());
console.log('[project-handlers:detectAutoBuildSourcePath] Checking paths:', possiblePaths);
console.warn('[project-handlers:detectAutoBuildSourcePath] Platform:', process.platform);
console.warn('[project-handlers:detectAutoBuildSourcePath] Is dev:', is.dev);
console.warn('[project-handlers:detectAutoBuildSourcePath] __dirname:', __dirname);
console.warn('[project-handlers:detectAutoBuildSourcePath] app.getAppPath():', app.getAppPath());
console.warn('[project-handlers:detectAutoBuildSourcePath] process.cwd():', process.cwd());
console.warn('[project-handlers:detectAutoBuildSourcePath] Checking paths:', possiblePaths);
}
for (const p of possiblePaths) {
@@ -154,11 +154,11 @@ const detectAutoBuildSourcePath = (): string | null => {
const exists = existsSync(p) && existsSync(markerPath);
if (debug) {
console.log(`[project-handlers:detectAutoBuildSourcePath] Checking ${p}: ${exists ? '✓ FOUND' : '✗ not found'}`);
console.warn(`[project-handlers:detectAutoBuildSourcePath] Checking ${p}: ${exists ? '✓ FOUND' : '✗ not found'}`);
}
if (exists) {
console.log(`[project-handlers:detectAutoBuildSourcePath] Auto-detected source path: ${p}`);
console.warn(`[project-handlers:detectAutoBuildSourcePath] Auto-detected source path: ${p}`);
return p;
}
}
@@ -197,7 +197,7 @@ const configureServicesWithPython = (
autoBuildPath: string,
agentManager: AgentManager
): void => {
console.log('[IPC] Configuring services with Python:', pythonPath);
console.warn('[IPC] Configuring services with Python:', pythonPath);
agentManager.configure(pythonPath, autoBuildPath);
changelogService.configure(pythonPath, autoBuildPath);
insightsService.configure(pythonPath, autoBuildPath);
@@ -213,7 +213,7 @@ const initializePythonEnvironment = async (
): Promise<PythonEnvStatus> => {
const autoBuildSource = getAutoBuildSourcePath();
if (!autoBuildSource) {
console.log('[IPC] Auto-build source not found, skipping Python env init');
console.warn('[IPC] Auto-build source not found, skipping Python env init');
return {
ready: false,
pythonPath: null,
@@ -223,7 +223,7 @@ const initializePythonEnvironment = async (
};
}
console.log('[IPC] Initializing Python environment...');
console.warn('[IPC] Initializing Python environment...');
const status = await pythonEnvManager.initialize(autoBuildSource);
if (status.ready && status.pythonPath) {
@@ -280,11 +280,11 @@ export function registerProjectHandlers(
// If a folder was deleted, reset autoBuildPath so UI prompts for reinitialization
const resetIds = projectStore.validateProjects();
if (resetIds.length > 0) {
console.log('[IPC] PROJECT_LIST: Detected missing .auto-claude folders for', resetIds.length, 'project(s)');
console.warn('[IPC] PROJECT_LIST: Detected missing .auto-claude folders for', resetIds.length, 'project(s)');
}
const projects = projectStore.getProjects();
console.log('[IPC] PROJECT_LIST returning', projects.length, 'projects');
console.warn('[IPC] PROJECT_LIST returning', projects.length, 'projects');
return { success: true, data: projects };
}
);
@@ -332,7 +332,7 @@ export function registerProjectHandlers(
// Initialize Python environment on startup (non-blocking)
initializePythonEnvironment(pythonEnvManager, agentManager).then((status) => {
console.log('[IPC] Python environment initialized:', status);
console.warn('[IPC] Python environment initialized:', status);
});
// IPC handler to get Python environment status
@@ -5,7 +5,6 @@ import type { IPCResult, Roadmap, RoadmapFeature, RoadmapFeatureStatus, RoadmapG
import path from 'path';
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs';
import { projectStore } from '../project-store';
import { fileWatcher } from '../file-watcher';
import { AgentManager } from '../agent';
@@ -51,12 +51,12 @@ const detectAutoBuildSourcePath = (): string | null => {
const debug = process.env.AUTO_CLAUDE_DEBUG === '1' || process.env.AUTO_CLAUDE_DEBUG === 'true';
if (debug) {
console.log('[detectAutoBuildSourcePath] Platform:', process.platform);
console.log('[detectAutoBuildSourcePath] Is dev:', is.dev);
console.log('[detectAutoBuildSourcePath] __dirname:', __dirname);
console.log('[detectAutoBuildSourcePath] app.getAppPath():', app.getAppPath());
console.log('[detectAutoBuildSourcePath] process.cwd():', process.cwd());
console.log('[detectAutoBuildSourcePath] Checking paths:', possiblePaths);
console.warn('[detectAutoBuildSourcePath] Platform:', process.platform);
console.warn('[detectAutoBuildSourcePath] Is dev:', is.dev);
console.warn('[detectAutoBuildSourcePath] __dirname:', __dirname);
console.warn('[detectAutoBuildSourcePath] app.getAppPath():', app.getAppPath());
console.warn('[detectAutoBuildSourcePath] process.cwd():', process.cwd());
console.warn('[detectAutoBuildSourcePath] Checking paths:', possiblePaths);
}
for (const p of possiblePaths) {
@@ -65,11 +65,11 @@ const detectAutoBuildSourcePath = (): string | null => {
const exists = existsSync(p) && existsSync(markerPath);
if (debug) {
console.log(`[detectAutoBuildSourcePath] Checking ${p}: ${exists ? '✓ FOUND' : '✗ not found'}`);
console.warn(`[detectAutoBuildSourcePath] Checking ${p}: ${exists ? '✓ FOUND' : '✗ not found'}`);
}
if (exists) {
console.log(`[detectAutoBuildSourcePath] Auto-detected source path: ${p}`);
console.warn(`[detectAutoBuildSourcePath] Auto-detected source path: ${p}`);
return p;
}
}
@@ -18,12 +18,12 @@ export function registerTaskArchiveHandlers(): void {
taskIds: string[],
version?: string
): Promise<IPCResult<boolean>> => {
console.log('[IPC] TASK_ARCHIVE called with projectId:', projectId, 'taskIds:', taskIds);
console.warn('[IPC] TASK_ARCHIVE called with projectId:', projectId, 'taskIds:', taskIds);
const result = projectStore.archiveTasks(projectId, taskIds, version);
if (result) {
console.log('[IPC] TASK_ARCHIVE success');
console.warn('[IPC] TASK_ARCHIVE success');
return { success: true, data: true };
} else {
console.error('[IPC] TASK_ARCHIVE failed');
@@ -38,12 +38,12 @@ export function registerTaskArchiveHandlers(): void {
ipcMain.handle(
IPC_CHANNELS.TASK_UNARCHIVE,
async (_, projectId: string, taskIds: string[]): Promise<IPCResult<boolean>> => {
console.log('[IPC] TASK_UNARCHIVE called with projectId:', projectId, 'taskIds:', taskIds);
console.warn('[IPC] TASK_UNARCHIVE called with projectId:', projectId, 'taskIds:', taskIds);
const result = projectStore.unarchiveTasks(projectId, taskIds);
if (result) {
console.log('[IPC] TASK_UNARCHIVE success');
console.warn('[IPC] TASK_UNARCHIVE success');
return { success: true, data: true };
} else {
console.error('[IPC] TASK_UNARCHIVE failed');
@@ -1,6 +1,6 @@
import { ipcMain } from 'electron';
import { IPC_CHANNELS, AUTO_BUILD_PATHS, getSpecsDir } from '../../../shared/constants';
import type { IPCResult, Task, TaskMetadata, Project } from '../../../shared/types';
import type { IPCResult, Task, TaskMetadata } from '../../../shared/types';
import path from 'path';
import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync } from 'fs';
import { projectStore } from '../../project-store';
@@ -18,9 +18,9 @@ export function registerTaskCRUDHandlers(agentManager: AgentManager): void {
ipcMain.handle(
IPC_CHANNELS.TASK_LIST,
async (_, projectId: string): Promise<IPCResult<Task[]>> => {
console.log('[IPC] TASK_LIST called with projectId:', projectId);
console.warn('[IPC] TASK_LIST called with projectId:', projectId);
const tasks = projectStore.getTasks(projectId);
console.log('[IPC] TASK_LIST returning', tasks.length, 'tasks');
console.warn('[IPC] TASK_LIST returning', tasks.length, 'tasks');
return { success: true, data: tasks };
}
);
@@ -45,17 +45,17 @@ export function registerTaskCRUDHandlers(agentManager: AgentManager): void {
// Auto-generate title if empty using Claude AI
let finalTitle = title;
if (!title || !title.trim()) {
console.log('[TASK_CREATE] Title is empty, generating with Claude AI...');
console.warn('[TASK_CREATE] Title is empty, generating with Claude AI...');
try {
const generatedTitle = await titleGenerator.generateTitle(description);
if (generatedTitle) {
finalTitle = generatedTitle;
console.log('[TASK_CREATE] Generated title:', finalTitle);
console.warn('[TASK_CREATE] Generated title:', finalTitle);
} else {
// Fallback: create title from first line of description
finalTitle = description.split('\n')[0].substring(0, 60);
if (finalTitle.length === 60) finalTitle += '...';
console.log('[TASK_CREATE] AI generation failed, using fallback:', finalTitle);
console.warn('[TASK_CREATE] AI generation failed, using fallback:', finalTitle);
}
} catch (err) {
console.error('[TASK_CREATE] Title generation error:', err);
@@ -226,7 +226,7 @@ export function registerTaskCRUDHandlers(agentManager: AgentManager): void {
try {
if (existsSync(specDir)) {
await rm(specDir, { recursive: true, force: true });
console.log(`[TASK_DELETE] Deleted spec directory: ${specDir}`);
console.warn(`[TASK_DELETE] Deleted spec directory: ${specDir}`);
}
return { success: true };
} catch (error) {
@@ -269,17 +269,17 @@ export function registerTaskCRUDHandlers(agentManager: AgentManager): void {
if (updates.title !== undefined && !updates.title.trim()) {
// Get description to use for title generation
const descriptionToUse = updates.description ?? task.description;
console.log('[TASK_UPDATE] Title is empty, generating with Claude AI...');
console.warn('[TASK_UPDATE] Title is empty, generating with Claude AI...');
try {
const generatedTitle = await titleGenerator.generateTitle(descriptionToUse);
if (generatedTitle) {
finalTitle = generatedTitle;
console.log('[TASK_UPDATE] Generated title:', finalTitle);
console.warn('[TASK_UPDATE] Generated title:', finalTitle);
} else {
// Fallback: create title from first line of description
finalTitle = descriptionToUse.split('\n')[0].substring(0, 60);
if (finalTitle.length === 60) finalTitle += '...';
console.log('[TASK_UPDATE] AI generation failed, using fallback:', finalTitle);
console.warn('[TASK_UPDATE] AI generation failed, using fallback:', finalTitle);
}
} catch (err) {
console.error('[TASK_UPDATE] Title generation error:', err);
@@ -21,10 +21,10 @@ export function registerTaskExecutionHandlers(
ipcMain.on(
IPC_CHANNELS.TASK_START,
(_, taskId: string, _options?: TaskStartOptions) => {
console.log('[TASK_START] Received request for taskId:', taskId);
console.warn('[TASK_START] Received request for taskId:', taskId);
const mainWindow = getMainWindow();
if (!mainWindow) {
console.log('[TASK_START] No main window found');
console.warn('[TASK_START] No main window found');
return;
}
@@ -32,7 +32,7 @@ export function registerTaskExecutionHandlers(
const { task, project } = findTaskAndProject(taskId);
if (!task || !project) {
console.log('[TASK_START] Task or project not found for taskId:', taskId);
console.warn('[TASK_START] Task or project not found for taskId:', taskId);
mainWindow.webContents.send(
IPC_CHANNELS.TASK_ERROR,
taskId,
@@ -44,7 +44,7 @@ export function registerTaskExecutionHandlers(
// Check git status - Auto Claude requires git for worktree-based builds
const gitStatus = checkGitStatus(project.path);
if (!gitStatus.isGitRepo) {
console.log('[TASK_START] Project is not a git repository:', project.path);
console.warn('[TASK_START] Project is not a git repository:', project.path);
mainWindow.webContents.send(
IPC_CHANNELS.TASK_ERROR,
taskId,
@@ -53,7 +53,7 @@ export function registerTaskExecutionHandlers(
return;
}
if (!gitStatus.hasCommits) {
console.log('[TASK_START] Git repository has no commits:', project.path);
console.warn('[TASK_START] Git repository has no commits:', project.path);
mainWindow.webContents.send(
IPC_CHANNELS.TASK_ERROR,
taskId,
@@ -62,7 +62,7 @@ export function registerTaskExecutionHandlers(
return;
}
console.log('[TASK_START] Found task:', task.specId, 'status:', task.status, 'subtasks:', task.subtasks.length);
console.warn('[TASK_START] Found task:', task.specId, 'status:', task.status, 'subtasks:', task.subtasks.length);
// Start file watcher for this task
const specsBaseDir = getSpecsDir(project.autoBuildPath);
@@ -82,7 +82,7 @@ export function registerTaskExecutionHandlers(
const needsSpecCreation = !hasSpec;
const needsImplementation = hasSpec && task.subtasks.length === 0;
console.log('[TASK_START] hasSpec:', hasSpec, 'needsSpecCreation:', needsSpecCreation, 'needsImplementation:', needsImplementation);
console.warn('[TASK_START] hasSpec:', hasSpec, 'needsSpecCreation:', needsSpecCreation, 'needsImplementation:', needsImplementation);
// Get base branch from project settings for worktree creation
const baseBranch = project.settings?.mainBranch;
@@ -90,7 +90,7 @@ export function registerTaskExecutionHandlers(
if (needsSpecCreation) {
// No spec file - need to run spec_runner.py to create the spec
const taskDescription = task.description || task.title;
console.log('[TASK_START] Starting spec creation for:', task.specId, 'in:', specDir);
console.warn('[TASK_START] Starting spec creation for:', task.specId, 'in:', specDir);
// Start spec creation process - pass the existing spec directory
// so spec_runner uses it instead of creating a new one
@@ -105,7 +105,7 @@ export function registerTaskExecutionHandlers(
// Use default description
}
console.log('[TASK_START] Starting task execution (no subtasks) for:', task.specId);
console.warn('[TASK_START] Starting task execution (no subtasks) for:', task.specId);
// Start task execution which will create the implementation plan
// Note: No parallel mode for planning phase - parallel only makes sense with multiple subtasks
agentManager.startTaskExecution(
@@ -121,7 +121,7 @@ export function registerTaskExecutionHandlers(
} else {
// Task has subtasks, start normal execution
// Note: Parallel execution is handled internally by the agent, not via CLI flags
console.log('[TASK_START] Starting task execution (has subtasks) for:', task.specId);
console.warn('[TASK_START] Starting task execution (has subtasks) for:', task.specId);
agentManager.startTaskExecution(
taskId,
@@ -313,7 +313,7 @@ export function registerTaskExecutionHandlers(
// Check git status before auto-starting
const gitStatusCheck = checkGitStatus(project.path);
if (!gitStatusCheck.isGitRepo || !gitStatusCheck.hasCommits) {
console.log('[TASK_UPDATE_STATUS] Git check failed, cannot auto-start task');
console.warn('[TASK_UPDATE_STATUS] Git check failed, cannot auto-start task');
if (mainWindow) {
mainWindow.webContents.send(
IPC_CHANNELS.TASK_ERROR,
@@ -324,7 +324,7 @@ export function registerTaskExecutionHandlers(
return { success: false, error: gitStatusCheck.error || 'Git repository required' };
}
console.log('[TASK_UPDATE_STATUS] Auto-starting task:', taskId);
console.warn('[TASK_UPDATE_STATUS] Auto-starting task:', taskId);
// Start file watcher for this task
fileWatcher.watch(taskId, specDir);
@@ -335,16 +335,16 @@ export function registerTaskExecutionHandlers(
const needsSpecCreation = !hasSpec;
const needsImplementation = hasSpec && task.subtasks.length === 0;
console.log('[TASK_UPDATE_STATUS] hasSpec:', hasSpec, 'needsSpecCreation:', needsSpecCreation, 'needsImplementation:', needsImplementation);
console.warn('[TASK_UPDATE_STATUS] hasSpec:', hasSpec, 'needsSpecCreation:', needsSpecCreation, 'needsImplementation:', needsImplementation);
if (needsSpecCreation) {
// No spec file - need to run spec_runner.py to create the spec
const taskDescription = task.description || task.title;
console.log('[TASK_UPDATE_STATUS] Starting spec creation for:', task.specId);
console.warn('[TASK_UPDATE_STATUS] Starting spec creation for:', task.specId);
agentManager.startSpecCreation(task.specId, project.path, taskDescription, specDir, task.metadata);
} else if (needsImplementation) {
// Spec exists but no subtasks - run run.py to create implementation plan and execute
console.log('[TASK_UPDATE_STATUS] Starting task execution (no subtasks) for:', task.specId);
console.warn('[TASK_UPDATE_STATUS] Starting task execution (no subtasks) for:', task.specId);
agentManager.startTaskExecution(
taskId,
project.path,
@@ -357,7 +357,7 @@ export function registerTaskExecutionHandlers(
} else {
// Task has subtasks, start normal execution
// Note: Parallel execution is handled internally by the agent
console.log('[TASK_UPDATE_STATUS] Starting task execution (has subtasks) for:', task.specId);
console.warn('[TASK_UPDATE_STATUS] Starting task execution (has subtasks) for:', task.specId);
agentManager.startTaskExecution(
taskId,
project.path,
@@ -538,7 +538,7 @@ export function registerTaskExecutionHandlers(
// Check git status before auto-restarting
const gitStatusForRestart = checkGitStatus(project.path);
if (!gitStatusForRestart.isGitRepo || !gitStatusForRestart.hasCommits) {
console.log('[Recovery] Git check failed, cannot auto-restart task');
console.warn('[Recovery] Git check failed, cannot auto-restart task');
// Recovery succeeded but we can't restart without git
return {
success: true,
@@ -577,11 +577,11 @@ export function registerTaskExecutionHandlers(
if (needsSpecCreation) {
// No spec file - need to run spec_runner.py to create the spec
const taskDescription = task.description || task.title;
console.log(`[Recovery] Starting spec creation for: ${task.specId}`);
console.warn(`[Recovery] Starting spec creation for: ${task.specId}`);
agentManager.startSpecCreation(task.specId, project.path, taskDescription, specDirForWatcher, task.metadata);
} else {
// Spec exists - run task execution
console.log(`[Recovery] Starting task execution for: ${task.specId}`);
console.warn(`[Recovery] Starting task execution for: ${task.specId}`);
agentManager.startTaskExecution(
taskId,
project.path,
@@ -594,7 +594,7 @@ export function registerTaskExecutionHandlers(
}
autoRestarted = true;
console.log(`[Recovery] Auto-restarted task ${taskId}`);
console.warn(`[Recovery] Auto-restarted task ${taskId}`);
} catch (restartError) {
console.error('Failed to auto-restart task after recovery:', restartError);
// Recovery succeeded but restart failed - still report success
@@ -1,5 +1,5 @@
import { ipcMain, BrowserWindow } from 'electron';
import { IPC_CHANNELS, AUTO_BUILD_PATHS, getSpecsDir } from '../../../shared/constants';
import { IPC_CHANNELS, AUTO_BUILD_PATHS } from '../../../shared/constants';
import type { IPCResult, WorktreeStatus, WorktreeDiff, WorktreeDiffFile, WorktreeMergeResult, WorktreeDiscardResult, WorktreeListResult, WorktreeListItem } from '../../../shared/types';
import path from 'path';
import { existsSync, readdirSync, statSync } from 'fs';
@@ -226,11 +226,11 @@ export function registerWorktreeHandlers(
async (_, taskId: string, options?: { noCommit?: boolean }): Promise<IPCResult<WorktreeMergeResult>> => {
// Always log merge operations for debugging
const debug = (...args: unknown[]) => {
console.log('[MERGE DEBUG]', ...args);
console.warn('[MERGE DEBUG]', ...args);
};
try {
console.log('[MERGE] Handler called with taskId:', taskId, 'options:', options);
console.warn('[MERGE] Handler called with taskId:', taskId, 'options:', options);
debug('Starting merge for taskId:', taskId, 'options:', options);
// Ensure Python environment is ready
@@ -501,11 +501,11 @@ export function registerWorktreeHandlers(
ipcMain.handle(
IPC_CHANNELS.TASK_WORKTREE_MERGE_PREVIEW,
async (_, taskId: string): Promise<IPCResult<WorktreeMergeResult>> => {
console.log('[IPC] TASK_WORKTREE_MERGE_PREVIEW called with taskId:', taskId);
console.warn('[IPC] TASK_WORKTREE_MERGE_PREVIEW called with taskId:', taskId);
try {
// Ensure Python environment is ready
if (!pythonEnvManager.isEnvReady()) {
console.log('[IPC] Python environment not ready, initializing...');
console.warn('[IPC] Python environment not ready, initializing...');
const autoBuildSource = getEffectiveSourcePath();
if (autoBuildSource) {
const status = await pythonEnvManager.initialize(autoBuildSource);
@@ -524,7 +524,7 @@ export function registerWorktreeHandlers(
console.error('[IPC] Task not found:', taskId);
return { success: false, error: 'Task not found' };
}
console.log('[IPC] Found task:', task.specId, 'project:', project.name);
console.warn('[IPC] Found task:', task.specId, 'project:', project.name);
// Check for uncommitted changes in the main project
let hasUncommittedChanges = false;
@@ -541,7 +541,7 @@ export function registerWorktreeHandlers(
.filter(line => line.trim())
.map(line => line.substring(3).trim()); // Remove status prefix (e.g., "M ", " M ", "?? ")
hasUncommittedChanges = uncommittedFiles.length > 0;
console.log('[IPC] Uncommitted changes detected:', uncommittedFiles.length, 'files');
console.warn('[IPC] Uncommitted changes detected:', uncommittedFiles.length, 'files');
}
} catch (e) {
console.error('[IPC] Failed to check git status:', e);
@@ -562,7 +562,7 @@ export function registerWorktreeHandlers(
];
const pythonPath = pythonEnvManager.getPythonPath() || 'python3';
console.log('[IPC] Running merge preview:', pythonPath, args.join(' '));
console.warn('[IPC] Running merge preview:', pythonPath, args.join(' '));
// Get profile environment for consistency
const previewProfileEnv = getProfileEnv();
@@ -579,22 +579,22 @@ export function registerWorktreeHandlers(
previewProcess.stdout.on('data', (data: Buffer) => {
const chunk = data.toString();
stdout += chunk;
console.log('[IPC] merge-preview stdout:', chunk);
console.warn('[IPC] merge-preview stdout:', chunk);
});
previewProcess.stderr.on('data', (data: Buffer) => {
const chunk = data.toString();
stderr += chunk;
console.log('[IPC] merge-preview stderr:', chunk);
console.warn('[IPC] merge-preview stderr:', chunk);
});
previewProcess.on('close', (code: number) => {
console.log('[IPC] merge-preview process exited with code:', code);
console.warn('[IPC] merge-preview process exited with code:', code);
if (code === 0) {
try {
// Parse JSON output from Python
const result = JSON.parse(stdout.trim());
console.log('[IPC] merge-preview result:', JSON.stringify(result, null, 2));
console.warn('[IPC] merge-preview result:', JSON.stringify(result, null, 2));
resolve({
success: true,
data: {
@@ -208,7 +208,7 @@ export function registerTerminalHandlers(
const { mkdirSync, existsSync } = await import('fs');
if (!existsSync(profile.configDir)) {
mkdirSync(profile.configDir, { recursive: true });
console.log('[IPC] Created config directory:', profile.configDir);
console.warn('[IPC] Created config directory:', profile.configDir);
}
}
@@ -217,7 +217,7 @@ export function registerTerminalHandlers(
const terminalId = `claude-login-${profileId}-${Date.now()}`;
const homeDir = process.env.HOME || process.env.USERPROFILE || '/tmp';
console.log('[IPC] Initializing Claude profile:', {
console.warn('[IPC] Initializing Claude profile:', {
profileId,
profileName: profile.name,
configDir: profile.configDir,
@@ -240,7 +240,7 @@ export function registerTerminalHandlers(
loginCommand = 'claude setup-token';
}
console.log('[IPC] Sending login command to terminal:', loginCommand);
console.warn('[IPC] Sending login command to terminal:', loginCommand);
// Write the login command to the terminal
terminalManager.write(terminalId, `${loginCommand}\r`);
@@ -255,12 +255,12 @@ export function registerTerminalHandlers(
});
}
return {
success: true,
data: {
return {
success: true,
data: {
terminalId,
message: `A terminal has been opened to authenticate "${profile.name}". Complete the OAuth flow in your browser, then copy the token shown in the terminal.`
}
}
};
} catch (error) {
console.error('[IPC] Failed to initialize Claude profile:', error);
@@ -569,5 +569,5 @@ export function initializeUsageMonitorForwarding(mainWindow: BrowserWindow): voi
mainWindow.webContents.send(IPC_CHANNELS.PROACTIVE_SWAP_NOTIFICATION, notification);
});
console.log('[terminal-handlers] Usage monitor event forwarding initialized');
console.warn('[terminal-handlers] Usage monitor event forwarding initialized');
}
+16 -17
View File
@@ -17,7 +17,7 @@ export interface LogEntry {
/**
* Service for persisting and retrieving task execution logs
*
*
* Log files are stored in {specDir}/logs/ with format:
* - session-{ISO-timestamp}.log - Raw log output per execution session
* - latest.log - Copy of most recent session's logs
@@ -26,7 +26,7 @@ export class LogService {
private activeSessions: Map<string, { sessionId: string; logPath: string; startedAt: Date }> = new Map();
private logBuffers: Map<string, string[]> = new Map();
private flushIntervals: Map<string, NodeJS.Timeout> = new Map();
// Flush logs to disk every 2 seconds to balance performance vs data safety
private readonly FLUSH_INTERVAL_MS = 2000;
// Maximum log file size before rotation (10MB)
@@ -39,7 +39,7 @@ export class LogService {
*/
startSession(taskId: string, specDir: string): string {
const logsDir = path.join(specDir, 'logs');
// Ensure logs directory exists
if (!existsSync(logsDir)) {
mkdirSync(logsDir, { recursive: true });
@@ -60,7 +60,7 @@ export class LogService {
'='.repeat(80),
''
].join('\n');
writeFileSync(logFile, header);
// Track active session
@@ -82,7 +82,7 @@ export class LogService {
// Clean up old sessions
this.cleanupOldSessions(logsDir);
console.log(`[LogService] Started session ${sessionId} for task ${taskId}`);
console.warn(`[LogService] Started session ${sessionId} for task ${taskId}`);
return sessionId;
}
@@ -120,7 +120,7 @@ export class LogService {
private flushBuffer(taskId: string): void {
const session = this.activeSessions.get(taskId);
const buffer = this.logBuffers.get(taskId);
if (!session || !buffer || buffer.length === 0) {
return;
}
@@ -150,7 +150,7 @@ export class LogService {
const now = new Date();
const duration = now.getTime() - session.startedAt.getTime();
const durationStr = this.formatDuration(duration);
const footer = [
'',
'='.repeat(80),
@@ -181,7 +181,7 @@ export class LogService {
this.activeSessions.delete(taskId);
this.logBuffers.delete(taskId);
console.log(`[LogService] Ended session for task ${taskId}, exit code: ${exitCode}`);
console.warn(`[LogService] Ended session for task ${taskId}, exit code: ${exitCode}`);
}
/**
@@ -189,7 +189,7 @@ export class LogService {
*/
getSessions(specDir: string): LogSession[] {
const logsDir = path.join(specDir, 'logs');
if (!existsSync(logsDir)) {
return [];
}
@@ -203,7 +203,7 @@ export class LogService {
const filePath = path.join(logsDir, file);
const stats = statSync(filePath);
const sessionId = file.replace('session-', '').replace('.log', '');
// Parse session ID back to date
const dateStr = sessionId.replace(/-/g, (match, offset) => {
// Replace first 2 dashes with actual dashes, rest with colons
@@ -211,7 +211,7 @@ export class LogService {
if (offset === 10) return 'T';
return ':';
}).replace(/-(\d{3})Z$/, '.$1Z');
const startedAt = new Date(dateStr);
// Count lines (approximate)
@@ -233,7 +233,7 @@ export class LogService {
*/
loadSessionLogs(specDir: string, sessionId?: string): string {
const logsDir = path.join(specDir, 'logs');
if (!existsSync(logsDir)) {
return '';
}
@@ -289,17 +289,17 @@ export class LogService {
// Keep MAX_SESSIONS_TO_KEEP, delete the rest
const toDelete = files.slice(this.MAX_SESSIONS_TO_KEEP);
for (const file of toDelete) {
const filePath = path.join(logsDir, file);
try {
require('fs').unlinkSync(filePath);
console.log(`[LogService] Deleted old log session: ${file}`);
} catch (e) {
console.warn(`[LogService] Deleted old log session: ${file}`);
} catch (_e) {
// Ignore deletion errors
}
}
} catch (error) {
} catch (_error) {
// Ignore cleanup errors
}
}
@@ -339,4 +339,3 @@ export class LogService {
// Singleton instance
export const logService = new LogService();
@@ -10,9 +10,9 @@ const DEBUG = process.env.AUTO_CLAUDE_DEBUG === 'true' || process.env.AUTO_CLAUD
function debug(message: string, data?: Record<string, unknown>): void {
if (DEBUG) {
if (data) {
console.log(`[ProjectInitializer] ${message}`, JSON.stringify(data, null, 2));
console.warn(`[ProjectInitializer] ${message}`, JSON.stringify(data, null, 2));
} else {
console.log(`[ProjectInitializer] ${message}`);
console.warn(`[ProjectInitializer] ${message}`);
}
}
}
+8 -8
View File
@@ -145,13 +145,13 @@ export class ProjectStore {
// Check if the project path still exists
if (!existsSync(project.path)) {
console.log(`[ProjectStore] Project path no longer exists: ${project.path}`);
console.warn(`[ProjectStore] Project path no longer exists: ${project.path}`);
continue; // Don't reset - let user handle this case
}
// Check if .auto-claude folder still exists
if (!isInitialized(project.path)) {
console.log(`[ProjectStore] .auto-claude folder missing for project "${project.name}" at ${project.path}`);
console.warn(`[ProjectStore] .auto-claude folder missing for project "${project.name}" at ${project.path}`);
project.autoBuildPath = '';
project.updatedAt = new Date();
resetProjectIds.push(project.id);
@@ -161,7 +161,7 @@ export class ProjectStore {
if (hasChanges) {
this.save();
console.log(`[ProjectStore] Reset ${resetProjectIds.length} project(s) due to missing .auto-claude folder`);
console.warn(`[ProjectStore] Reset ${resetProjectIds.length} project(s) due to missing .auto-claude folder`);
}
return resetProjectIds;
@@ -194,18 +194,18 @@ export class ProjectStore {
* Get tasks for a project by scanning specs directory
*/
getTasks(projectId: string): Task[] {
console.log('[ProjectStore] getTasks called with projectId:', projectId);
console.warn('[ProjectStore] getTasks called with projectId:', projectId);
const project = this.getProject(projectId);
if (!project) {
console.log('[ProjectStore] Project not found for id:', projectId);
console.warn('[ProjectStore] Project not found for id:', projectId);
return [];
}
console.log('[ProjectStore] Found project:', project.name, 'autoBuildPath:', project.autoBuildPath);
console.warn('[ProjectStore] Found project:', project.name, 'autoBuildPath:', project.autoBuildPath);
// Get specs directory path
const specsBaseDir = getSpecsDir(project.autoBuildPath);
const specsDir = path.join(project.path, specsBaseDir);
console.log('[ProjectStore] specsDir:', specsDir, 'exists:', existsSync(specsDir));
console.warn('[ProjectStore] specsDir:', specsDir, 'exists:', existsSync(specsDir));
if (!existsSync(specsDir)) return [];
const tasks: Task[] = [];
@@ -312,7 +312,7 @@ export class ProjectStore {
}
}
console.log('[ProjectStore] Returning', tasks.length, 'tasks out of', specDirs.filter(d => d.isDirectory() && d.name !== '.gitkeep').length, 'spec directories');
console.warn('[ProjectStore] Returning', tasks.length, 'tasks out of', specDirs.filter(d => d.isDirectory() && d.name !== '.gitkeep').length, 'spec directories');
return tasks;
}
+11 -12
View File
@@ -1,5 +1,5 @@
import { spawn, execSync } from 'child_process';
import { existsSync, readFileSync } from 'fs';
import { existsSync } from 'fs';
import path from 'path';
import { EventEmitter } from 'events';
@@ -147,7 +147,7 @@ export class PythonEnvManager extends EventEmitter {
}
this.emit('status', 'Creating Python virtual environment...');
console.log('[PythonEnvManager] Creating venv with:', systemPython);
console.warn('[PythonEnvManager] Creating venv with:', systemPython);
return new Promise((resolve) => {
const venvPath = path.join(this.autoBuildSourcePath!, '.venv');
@@ -163,7 +163,7 @@ export class PythonEnvManager extends EventEmitter {
proc.on('close', (code) => {
if (code === 0) {
console.log('[PythonEnvManager] Venv created successfully');
console.warn('[PythonEnvManager] Venv created successfully');
resolve(true);
} else {
console.error('[PythonEnvManager] Failed to create venv:', stderr);
@@ -200,7 +200,7 @@ export class PythonEnvManager extends EventEmitter {
}
this.emit('status', 'Installing Python dependencies (this may take a minute)...');
console.log('[PythonEnvManager] Installing dependencies from:', requirementsPath);
console.warn('[PythonEnvManager] Installing dependencies from:', requirementsPath);
return new Promise((resolve) => {
const proc = spawn(venvPip, ['install', '-r', requirementsPath], {
@@ -228,7 +228,7 @@ export class PythonEnvManager extends EventEmitter {
proc.on('close', (code) => {
if (code === 0) {
console.log('[PythonEnvManager] Dependencies installed successfully');
console.warn('[PythonEnvManager] Dependencies installed successfully');
this.emit('status', 'Dependencies installed successfully');
resolve(true);
} else {
@@ -264,12 +264,12 @@ export class PythonEnvManager extends EventEmitter {
this.isInitializing = true;
this.autoBuildSourcePath = autoBuildSourcePath;
console.log('[PythonEnvManager] Initializing with path:', autoBuildSourcePath);
console.warn('[PythonEnvManager] Initializing with path:', autoBuildSourcePath);
try {
// Check if venv exists
if (!this.venvExists()) {
console.log('[PythonEnvManager] Venv not found, creating...');
console.warn('[PythonEnvManager] Venv not found, creating...');
const created = await this.createVenv();
if (!created) {
this.isInitializing = false;
@@ -282,13 +282,13 @@ export class PythonEnvManager extends EventEmitter {
};
}
} else {
console.log('[PythonEnvManager] Venv already exists');
console.warn('[PythonEnvManager] Venv already exists');
}
// Check if deps are installed
const depsInstalled = await this.checkDepsInstalled();
if (!depsInstalled) {
console.log('[PythonEnvManager] Dependencies not installed, installing...');
console.warn('[PythonEnvManager] Dependencies not installed, installing...');
const installed = await this.installDeps();
if (!installed) {
this.isInitializing = false;
@@ -301,7 +301,7 @@ export class PythonEnvManager extends EventEmitter {
};
}
} else {
console.log('[PythonEnvManager] Dependencies already installed');
console.warn('[PythonEnvManager] Dependencies already installed');
}
this.pythonPath = this.getVenvPythonPath();
@@ -309,7 +309,7 @@ export class PythonEnvManager extends EventEmitter {
this.isInitializing = false;
this.emit('ready', this.pythonPath);
console.log('[PythonEnvManager] Ready with Python path:', this.pythonPath);
console.warn('[PythonEnvManager] Ready with Python path:', this.pythonPath);
return {
ready: true,
@@ -362,4 +362,3 @@ export class PythonEnvManager extends EventEmitter {
// Singleton instance
export const pythonEnvManager = new PythonEnvManager();
@@ -144,7 +144,7 @@ export function getProfileEnv(profileId?: string): Record<string, string> {
? profileManager.getProfile(profileId)
: profileManager.getActiveProfile();
console.log('[getProfileEnv] Active profile:', {
console.warn('[getProfileEnv] Active profile:', {
profileId: profile?.id,
profileName: profile?.name,
email: profile?.email,
@@ -154,19 +154,19 @@ export function getProfileEnv(profileId?: string): Record<string, string> {
});
if (!profile) {
console.log('[getProfileEnv] No profile found, using defaults');
console.warn('[getProfileEnv] No profile found, using defaults');
return {};
}
// Prefer OAuth token (instant switching, no browser auth needed)
// Use profile manager to get decrypted token
if (profile.oauthToken) {
const decryptedToken = profileId
const decryptedToken = profileId
? profileManager.getProfileToken(profileId)
: profileManager.getActiveProfileToken();
if (decryptedToken) {
console.log('[getProfileEnv] Using OAuth token for profile:', profile.name);
console.warn('[getProfileEnv] Using OAuth token for profile:', profile.name);
return {
CLAUDE_CODE_OAUTH_TOKEN: decryptedToken
};
@@ -177,20 +177,20 @@ export function getProfileEnv(profileId?: string): Record<string, string> {
// Fallback: If default profile, no env vars needed
if (profile.isDefault) {
console.log('[getProfileEnv] Using default profile (no env vars)');
console.warn('[getProfileEnv] Using default profile (no env vars)');
return {};
}
// Fallback: Use configDir for profiles without OAuth token (legacy)
if (profile.configDir) {
console.log('[getProfileEnv] Using configDir fallback for profile:', profile.name);
console.warn('[getProfileEnv] Using configDir fallback for profile:', profile.name);
console.warn('[getProfileEnv] WARNING: Profile has no OAuth token. Run "claude setup-token" and save the token to enable instant switching.');
return {
CLAUDE_CONFIG_DIR: profile.configDir
};
}
console.log('[getProfileEnv] Profile has no auth method configured');
console.warn('[getProfileEnv] Profile has no auth method configured');
return {};
}
+7 -7
View File
@@ -1,5 +1,5 @@
import path from 'path';
import { existsSync, readFileSync, watchFile, unwatchFile, FSWatcher } from 'fs';
import { existsSync, readFileSync, watchFile } from 'fs';
import { EventEmitter } from 'events';
import type { TaskLogs, TaskLogPhase, TaskLogStreamChunk, TaskPhaseLog } from '../shared/types';
@@ -189,7 +189,7 @@ export class TaskLogService extends EventEmitter {
if (existsSync(mainLogFile)) {
try {
lastMainContent = readFileSync(mainLogFile, 'utf-8');
} catch (e) {
} catch (_e) {
// Ignore parse errors on initial load
}
}
@@ -200,7 +200,7 @@ export class TaskLogService extends EventEmitter {
if (existsSync(worktreeLogFile)) {
try {
lastWorktreeContent = readFileSync(worktreeLogFile, 'utf-8');
} catch (e) {
} catch (_e) {
// Ignore parse errors on initial load
}
}
@@ -225,7 +225,7 @@ export class TaskLogService extends EventEmitter {
lastMainContent = currentContent;
mainChanged = true;
}
} catch (error) {
} catch (_error) {
// Ignore read/parse errors
}
}
@@ -240,7 +240,7 @@ export class TaskLogService extends EventEmitter {
lastWorktreeContent = currentContent;
worktreeChanged = true;
}
} catch (error) {
} catch (_error) {
// Ignore read/parse errors
}
}
@@ -262,7 +262,7 @@ export class TaskLogService extends EventEmitter {
}, this.POLL_INTERVAL_MS);
this.pollIntervals.set(specId, pollInterval);
console.log(`[TaskLogService] Started watching ${specId} (main: ${specDir}${worktreeSpecDir ? `, worktree: ${worktreeSpecDir}` : ''})`);
console.warn(`[TaskLogService] Started watching ${specId} (main: ${specDir}${worktreeSpecDir ? `, worktree: ${worktreeSpecDir}` : ''})`);
}
/**
@@ -274,7 +274,7 @@ export class TaskLogService extends EventEmitter {
clearInterval(interval);
this.pollIntervals.delete(specId);
this.watchedPaths.delete(specId);
console.log(`[TaskLogService] Stopped watching ${specId}`);
console.warn(`[TaskLogService] Stopped watching ${specId}`);
}
}
@@ -12,7 +12,7 @@ const DEBUG = process.env.AUTO_CLAUDE_DEBUG === 'true' || process.env.AUTO_CLAUD
function debug(...args: unknown[]): void {
if (DEBUG) {
console.log('[TerminalNameGenerator]', ...args);
console.warn('[TerminalNameGenerator]', ...args);
}
}
@@ -100,7 +100,7 @@ export class TerminalSessionStore {
// Migrate from v1 to v2 structure
if (data.version === 1 && data.sessions) {
console.log('[TerminalSessionStore] Migrating from v1 to v2 structure');
console.warn('[TerminalSessionStore] Migrating from v1 to v2 structure');
const today = getDateString();
const migratedData: SessionData = {
version: STORE_VERSION,
@@ -115,7 +115,7 @@ export class TerminalSessionStore {
return data as SessionData;
}
console.log('[TerminalSessionStore] Version mismatch, resetting sessions');
console.warn('[TerminalSessionStore] Version mismatch, resetting sessions');
return { version: STORE_VERSION, sessionsByDate: {} };
}
} catch (error) {
@@ -155,7 +155,7 @@ export class TerminalSessionStore {
}
if (removedCount > 0) {
console.log(`[TerminalSessionStore] Cleaned up sessions from ${removedCount} old dates`);
console.warn(`[TerminalSessionStore] Cleaned up sessions from ${removedCount} old dates`);
this.save();
}
}
@@ -225,7 +225,7 @@ export class TerminalSessionStore {
if (dates.length > 0) {
const mostRecentDate = dates[0];
console.log(`[TerminalSessionStore] No sessions today, using sessions from ${mostRecentDate}`);
console.warn(`[TerminalSessionStore] No sessions today, using sessions from ${mostRecentDate}`);
return this.data.sessionsByDate[mostRecentDate][projectPath] || [];
}
@@ -361,7 +361,7 @@ export class TerminalSessionStore {
session.claudeSessionId = claudeSessionId;
session.isClaudeMode = true;
this.save();
console.log('[TerminalSessionStore] Saved Claude session ID:', claudeSessionId, 'for terminal:', terminalId);
console.warn('[TerminalSessionStore] Saved Claude session ID:', claudeSessionId, 'for terminal:', terminalId);
}
}
@@ -6,7 +6,6 @@
import * as os from 'os';
import * as fs from 'fs';
import * as path from 'path';
import type { BrowserWindow } from 'electron';
import { IPC_CHANNELS } from '../../shared/constants';
import { getClaudeProfileManager } from '../claude-profile-manager';
import * as OutputParser from './output-parser';
@@ -39,14 +38,14 @@ export function handleRateLimit(
}
lastNotifiedRateLimitReset.set(terminal.id, resetTime);
console.log('[ClaudeIntegration] Rate limit detected, reset:', resetTime);
console.warn('[ClaudeIntegration] Rate limit detected, reset:', resetTime);
const profileManager = getClaudeProfileManager();
const currentProfileId = terminal.claudeProfileId || 'default';
try {
const rateLimitEvent = profileManager.recordRateLimitEvent(currentProfileId, resetTime);
console.log('[ClaudeIntegration] Recorded rate limit event:', rateLimitEvent.type);
console.warn('[ClaudeIntegration] Recorded rate limit event:', rateLimitEvent.type);
} catch (err) {
console.error('[ClaudeIntegration] Failed to record rate limit event:', err);
}
@@ -68,9 +67,9 @@ export function handleRateLimit(
}
if (autoSwitchSettings.enabled && autoSwitchSettings.autoSwitchOnRateLimit && bestProfile) {
console.log('[ClaudeIntegration] Auto-switching to profile:', bestProfile.name);
switchProfileCallback(terminal.id, bestProfile.id).then(result => {
console.log('[ClaudeIntegration] Auto-switch completed');
console.warn('[ClaudeIntegration] Auto-switching to profile:', bestProfile.name);
switchProfileCallback(terminal.id, bestProfile.id).then(_result => {
console.warn('[ClaudeIntegration] Auto-switch completed');
}).catch(err => {
console.error('[ClaudeIntegration] Auto-switch failed:', err);
});
@@ -90,7 +89,7 @@ export function handleOAuthToken(
return;
}
console.log('[ClaudeIntegration] OAuth token detected, length:', token.length);
console.warn('[ClaudeIntegration] OAuth token detected, length:', token.length);
const email = OutputParser.extractEmail(terminal.outputBuffer);
const profileIdMatch = terminal.id.match(/claude-login-(profile-\d+)-/);
@@ -101,7 +100,7 @@ export function handleOAuthToken(
const success = profileManager.setProfileToken(profileId, token, email || undefined);
if (success) {
console.log('[ClaudeIntegration] OAuth token auto-saved to profile:', profileId);
console.warn('[ClaudeIntegration] OAuth token auto-saved to profile:', profileId);
const win = getWindow();
if (win) {
@@ -117,7 +116,7 @@ export function handleOAuthToken(
console.error('[ClaudeIntegration] Failed to save OAuth token to profile:', profileId);
}
} else {
console.log('[ClaudeIntegration] OAuth token detected but not in a profile login terminal');
console.warn('[ClaudeIntegration] OAuth token detected but not in a profile login terminal');
const win = getWindow();
if (win) {
win.webContents.send(IPC_CHANNELS.TERMINAL_OAUTH_TOKEN, {
@@ -140,7 +139,7 @@ export function handleClaudeSessionId(
getWindow: WindowGetter
): void {
terminal.claudeSessionId = sessionId;
console.log('[ClaudeIntegration] Captured Claude session ID:', sessionId);
console.warn('[ClaudeIntegration] Captured Claude session ID:', sessionId);
if (terminal.projectPath) {
SessionHandler.updateClaudeSessionId(terminal.projectPath, terminal.id, sessionId);
@@ -187,17 +186,17 @@ export function invokeClaude(
fs.writeFileSync(tempFile, `export CLAUDE_CODE_OAUTH_TOKEN="${token}"\n`, { mode: 0o600 });
terminal.pty.write(`${cwdCommand}source "${tempFile}" && rm -f "${tempFile}" && claude\r`);
console.log('[ClaudeIntegration] Switching to Claude profile:', activeProfile.name, '(via secure temp file)');
console.warn('[ClaudeIntegration] Switching to Claude profile:', activeProfile.name, '(via secure temp file)');
return;
} else if (activeProfile.configDir) {
terminal.pty.write(`${cwdCommand}CLAUDE_CONFIG_DIR="${activeProfile.configDir}" claude\r`);
console.log('[ClaudeIntegration] Using Claude profile:', activeProfile.name, 'config:', activeProfile.configDir);
console.warn('[ClaudeIntegration] Using Claude profile:', activeProfile.name, 'config:', activeProfile.configDir);
return;
}
}
if (activeProfile && !activeProfile.isDefault) {
console.log('[ClaudeIntegration] Using Claude profile:', activeProfile.name, '(from terminal environment)');
console.warn('[ClaudeIntegration] Using Claude profile:', activeProfile.name, '(from terminal environment)');
}
terminal.pty.write(`${cwdCommand}claude\r`);
@@ -265,7 +264,7 @@ export async function switchClaudeProfile(
return { success: false, error: 'Profile not found' };
}
console.log('[ClaudeIntegration] Switching to Claude profile:', profile.name);
console.warn('[ClaudeIntegration] Switching to Claude profile:', profile.name);
if (terminal.isClaudeMode) {
terminal.pty.write('\x03');
@@ -15,7 +15,20 @@ const SOCKET_PATH =
? `\\\\.\\pipe\\auto-claude-pty-${process.getuid?.() || 'default'}`
: `/tmp/auto-claude-pty-${process.getuid?.() || 'default'}.sock`;
type ResponseHandler = (response: any) => void;
interface DaemonResponseData {
exitCode?: number;
signal?: number;
}
interface DaemonResponse {
requestId?: string;
type: string;
id?: string;
data?: string | DaemonResponseData;
error?: string;
}
type ResponseHandler = (response: DaemonResponse) => void;
interface PtyConfig {
shell: string;
@@ -62,13 +75,13 @@ class PtyDaemonClient {
try {
// Try to connect to existing daemon
await this.tryConnect();
console.log('[PtyDaemonClient] Connected to existing daemon');
console.warn('[PtyDaemonClient] Connected to existing daemon');
} catch {
// Spawn daemon and connect
console.log('[PtyDaemonClient] Spawning new daemon...');
console.warn('[PtyDaemonClient] Spawning new daemon...');
await this.spawnDaemon();
await this.tryConnect();
console.log('[PtyDaemonClient] Connected to new daemon');
console.warn('[PtyDaemonClient] Connected to new daemon');
} finally {
this.isConnecting = false;
this.reconnectAttempts = 0;
@@ -119,7 +132,7 @@ class PtyDaemonClient {
// Unref so parent can exit independently
this.daemonProcess.unref();
console.log(`[PtyDaemonClient] Spawned daemon process (PID: ${this.daemonProcess.pid})`);
console.warn(`[PtyDaemonClient] Spawned daemon process (PID: ${this.daemonProcess.pid})`);
// Wait for daemon to start listening
await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -154,7 +167,7 @@ class PtyDaemonClient {
});
this.socket.on('close', () => {
console.log('[PtyDaemonClient] Disconnected from daemon');
console.warn('[PtyDaemonClient] Disconnected from daemon');
this.socket = null;
if (!this.isShuttingDown) {
this.attemptReconnect();
@@ -169,7 +182,7 @@ class PtyDaemonClient {
/**
* Handle response from daemon
*/
private handleResponse(response: any): void {
private handleResponse(response: DaemonResponse): void {
// Handle request-response pattern
if (response.requestId) {
const handler = this.pendingRequests.get(response.requestId);
@@ -183,7 +196,7 @@ class PtyDaemonClient {
// Handle streaming data
if (response.type === 'data' && response.id) {
const handler = this.dataHandlers.get(response.id);
if (handler) {
if (handler && typeof response.data === 'string') {
handler(response.data);
}
return;
@@ -192,8 +205,9 @@ class PtyDaemonClient {
// Handle exit events
if (response.type === 'exit' && response.id) {
const handler = this.exitHandlers.get(response.id);
if (handler) {
handler(response.data.exitCode, response.data.signal);
if (handler && typeof response.data === 'object' && response.data !== null) {
const exitData = response.data as DaemonResponseData;
handler(exitData.exitCode ?? 0, exitData.signal);
}
return;
}
@@ -213,7 +227,7 @@ class PtyDaemonClient {
this.reconnectAttempts++;
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 10000);
console.log(
console.warn(
`[PtyDaemonClient] Reconnect attempt ${this.reconnectAttempts} in ${delay}ms...`
);
@@ -227,7 +241,7 @@ class PtyDaemonClient {
/**
* Send a request and wait for response
*/
private async request<T>(msg: any): Promise<T> {
private async request<T>(msg: Record<string, unknown>): Promise<T> {
await this.connect();
if (!this.socket) {
@@ -258,7 +272,7 @@ class PtyDaemonClient {
/**
* Send a message without expecting a response
*/
private send(msg: any): void {
private send(msg: Record<string, unknown>): void {
if (!this.socket) {
console.warn('[PtyDaemonClient] Cannot send - not connected');
return;
+12 -10
View File
@@ -56,14 +56,14 @@ interface DaemonMessage {
| 'get-buffer'
| 'ping';
id?: string;
data?: any;
data?: unknown;
requestId?: string;
}
interface DaemonResponse {
type: 'created' | 'list' | 'buffer' | 'data' | 'exit' | 'error' | 'pong';
id?: string;
data?: any;
data?: unknown;
requestId?: string;
error?: string;
}
@@ -102,9 +102,9 @@ class PtyDaemon {
this.handleConnection(socket);
});
this.server.on('error', (err) => {
this.server.on('error', (err: NodeJS.ErrnoException) => {
console.error('[PTY Daemon] Server error:', err);
if ((err as any).code === 'EADDRINUSE') {
if (err.code === 'EADDRINUSE') {
console.error('[PTY Daemon] Address in use - another daemon may be running');
process.exit(1);
}
@@ -172,20 +172,22 @@ class PtyDaemon {
break;
case 'create': {
const id = this.createPty(msg.data);
const id = this.createPty(msg.data as PtyConfig);
this.send(socket, { type: 'created', id, requestId: msg.requestId });
break;
}
case 'write':
if (!msg.id) throw new Error('Missing PTY id');
this.writeToPty(msg.id, msg.data);
this.writeToPty(msg.id, msg.data as string);
break;
case 'resize':
case 'resize': {
if (!msg.id) throw new Error('Missing PTY id');
this.resizePty(msg.id, msg.data.cols, msg.data.rows);
const resizeData = msg.data as { cols: number; rows: number };
this.resizePty(msg.id, resizeData.cols, resizeData.rows);
break;
}
case 'kill':
if (!msg.id) throw new Error('Missing PTY id');
@@ -221,7 +223,7 @@ class PtyDaemon {
}
default:
throw new Error(`Unknown message type: ${(msg as any).type}`);
throw new Error(`Unknown message type: ${(msg as DaemonMessage).type}`);
}
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
@@ -428,7 +430,7 @@ class PtyDaemon {
socket.write(JSON.stringify(response) + '\n');
} catch {
// Socket may be closed, ignore
console.debug('[PTY Daemon] Failed to send response (socket closed?)');
console.warn('[PTY Daemon] Failed to send response (socket closed?)');
}
}
@@ -5,7 +5,7 @@
import * as pty from 'node-pty';
import * as os from 'os';
import type { TerminalProcess, WindowGetter, TerminalOperationResult } from './types';
import type { TerminalProcess, WindowGetter } from './types';
import { IPC_CHANNELS } from '../../shared/constants';
import { getClaudeProfileManager } from '../claude-profile-manager';
@@ -24,7 +24,7 @@ export function spawnPtyProcess(
const shellArgs = process.platform === 'win32' ? [] : ['-l'];
console.log('[PtyManager] Spawning shell:', shell, shellArgs);
console.warn('[PtyManager] Spawning shell:', shell, shellArgs);
return pty.spawn(shell, shellArgs, {
name: 'xterm-256color',
@@ -69,7 +69,7 @@ export function setupPtyHandlers(
// Handle terminal exit
ptyProcess.onExit(({ exitCode }) => {
console.log('[PtyManager] Terminal exited:', id, 'code:', exitCode);
console.warn('[PtyManager] Terminal exited:', id, 'code:', exitCode);
const win = getWindow();
if (win) {
@@ -6,7 +6,7 @@
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import type { TerminalProcess, WindowGetter, SessionCaptureResult } from './types';
import type { TerminalProcess, WindowGetter } from './types';
import { getTerminalSessionStore, type TerminalSession } from '../terminal-session-store';
import { IPC_CHANNELS } from '../../shared/constants';
import { debugLog, debugError } from '../../shared/utils/debug-logger';
@@ -115,7 +115,7 @@ export function persistSession(terminal: TerminalProcess): void {
* Persist all active sessions
*/
export function persistAllSessions(terminals: Map<string, TerminalProcess>): void {
const store = getTerminalSessionStore();
const _store = getTerminalSessionStore();
terminals.forEach((terminal) => {
if (terminal.projectPath) {
@@ -49,7 +49,7 @@ class SessionPersistence {
const sessions = this.loadSessions();
this.isInitialized = true;
console.log(`[SessionPersistence] Initialized with ${sessions.length} sessions`);
console.warn(`[SessionPersistence] Initialized with ${sessions.length} sessions`);
return this.getRecoveryInfo();
}
@@ -90,7 +90,7 @@ class SessionPersistence {
validSessions.forEach((s) => this.sessions.set(s.id, s));
console.log(
console.warn(
`[SessionPersistence] Loaded ${validSessions.length} valid sessions, cleaned ${staleSessions.length} stale sessions`
);
return validSessions;
@@ -188,7 +188,7 @@ class SessionPersistence {
fs.writeFileSync(bufferPath, serializedBuffer, 'utf8');
session.bufferFile = bufferFile;
this.saveSession(session);
console.debug(`[SessionPersistence] Saved buffer for session ${sessionId} (${serializedBuffer.length} bytes)`);
console.warn(`[SessionPersistence] Saved buffer for session ${sessionId} (${serializedBuffer.length} bytes)`);
} catch (error) {
console.error(`[SessionPersistence] Failed to save buffer for ${sessionId}:`, error);
}
@@ -223,7 +223,7 @@ class SessionPersistence {
if (fs.existsSync(bufferPath)) {
try {
fs.unlinkSync(bufferPath);
console.debug(`[SessionPersistence] Deleted buffer file: ${bufferFile}`);
console.warn(`[SessionPersistence] Deleted buffer file: ${bufferFile}`);
} catch (error) {
console.error(`[SessionPersistence] Failed to delete buffer file ${bufferFile}:`, error);
}
@@ -266,7 +266,7 @@ class SessionPersistence {
try {
fs.writeFileSync(SESSIONS_FILE, JSON.stringify(data, null, 2), 'utf8');
console.debug(`[SessionPersistence] Saved ${data.sessions.length} sessions to disk`);
console.warn(`[SessionPersistence] Saved ${data.sessions.length} sessions to disk`);
} catch (error) {
console.error('[SessionPersistence] Failed to save sessions:', error);
}
@@ -296,7 +296,7 @@ class SessionPersistence {
}
if (cleanedCount > 0) {
console.log(`[SessionPersistence] Cleaned up ${cleanedCount} orphaned buffer files`);
console.warn(`[SessionPersistence] Cleaned up ${cleanedCount} orphaned buffer files`);
}
} catch (error) {
console.error('[SessionPersistence] Failed to cleanup orphaned buffers:', error);
@@ -309,7 +309,7 @@ export const sessionPersistence = new SessionPersistence();
// Hook into app lifecycle
app.on('before-quit', () => {
console.log('[SessionPersistence] App quitting, saving sessions...');
console.warn('[SessionPersistence] App quitting, saving sessions...');
sessionPersistence.saveNow();
});
@@ -9,7 +9,6 @@ import { IPC_CHANNELS } from '../../shared/constants';
import type { TerminalSession } from '../terminal-session-store';
import * as PtyManager from './pty-manager';
import * as SessionHandler from './session-handler';
import * as TerminalEventHandler from './terminal-event-handler';
import type {
TerminalProcess,
WindowGetter,
@@ -226,8 +225,8 @@ export async function destroyAllTerminals(
* Sessions are only removed when explicitly destroyed by user action via destroyTerminal().
*/
function handleTerminalExit(
terminal: TerminalProcess,
terminals: Map<string, TerminalProcess>
_terminal: TerminalProcess,
_terminals: Map<string, TerminalProcess>
): void {
// Don't remove session - let it persist for restoration
}
+5 -5
View File
@@ -12,7 +12,7 @@ const DEBUG = process.env.AUTO_CLAUDE_DEBUG === 'true' || process.env.AUTO_CLAUD
function debug(...args: unknown[]): void {
if (DEBUG) {
console.log('[TitleGenerator]', ...args);
console.warn('[TitleGenerator]', ...args);
}
}
@@ -144,7 +144,7 @@ export class TitleGenerator extends EventEmitter {
let output = '';
let errorOutput = '';
const timeout = setTimeout(() => {
console.log('[TitleGenerator] Title generation timed out after 60s');
console.warn('[TitleGenerator] Title generation timed out after 60s');
childProcess.kill();
resolve(null);
}, 60000); // 60 second timeout for SDK initialization + API call
@@ -169,7 +169,7 @@ export class TitleGenerator extends EventEmitter {
const combinedOutput = `${output}\n${errorOutput}`;
const rateLimitDetection = detectRateLimit(combinedOutput);
if (rateLimitDetection.isRateLimited) {
console.log('[TitleGenerator] Rate limit detected:', {
console.warn('[TitleGenerator] Rate limit detected:', {
resetTime: rateLimitDetection.resetTime,
limitType: rateLimitDetection.limitType,
suggestedProfile: rateLimitDetection.suggestedProfile?.name
@@ -180,7 +180,7 @@ export class TitleGenerator extends EventEmitter {
}
// Always log failures to help diagnose issues
console.log('[TitleGenerator] Title generation failed', {
console.warn('[TitleGenerator] Title generation failed', {
code,
errorOutput: errorOutput.substring(0, 500),
output: output.substring(0, 200),
@@ -192,7 +192,7 @@ export class TitleGenerator extends EventEmitter {
childProcess.on('error', (err) => {
clearTimeout(timeout);
console.log('[TitleGenerator] Process error:', err.message);
console.warn('[TitleGenerator] Process error:', err.message);
resolve(null);
});
});
@@ -75,7 +75,7 @@ export function copyDirectoryRecursive(
const destPath = path.join(dest, entry.name);
// Skip certain files/directories
if (SKIP_FILES.includes(entry.name as any)) {
if (SKIP_FILES.includes(entry.name as (typeof SKIP_FILES)[number])) {
continue;
}
@@ -36,7 +36,7 @@ export function fetchJson<T>(url: string): Promise<T> {
response.on('end', () => {
try {
resolve(JSON.parse(data) as T);
} catch (e) {
} catch (_e) {
reject(new Error('Failed to parse JSON response'));
}
});
@@ -8,7 +8,7 @@ import { app } from 'electron';
import { GITHUB_CONFIG, PRESERVE_FILES } from './config';
import { downloadFile, fetchJson } from './http-client';
import { parseVersionFromTag } from './version-manager';
import { getUpdateCachePath, getUpdateTargetPath, getBundledSourcePath } from './path-resolver';
import { getUpdateCachePath, getUpdateTargetPath } from './path-resolver';
import { extractTarball, copyDirectoryRecursive, preserveFiles, restoreFiles, cleanTargetDirectory } from './file-operations';
import { getCachedRelease, setCachedRelease, clearCachedRelease } from './update-checker';
import { GitHubRelease, AutoBuildUpdateResult, UpdateProgressCallback, UpdateMetadata } from './types';
@@ -12,7 +12,7 @@ export type IpcListenerCleanup = () => void;
* @param callback - The callback function to execute when event is received
* @returns Cleanup function to remove the listener
*/
export function createIpcListener<T extends any[]>(
export function createIpcListener<T extends unknown[]>(
channel: string,
callback: (...args: T) => void
): IpcListenerCleanup {
@@ -32,7 +32,7 @@ export function createIpcListener<T extends any[]>(
* @param args - Arguments to pass to the IPC handler
* @returns Promise with the typed result
*/
export function invokeIpc<T>(channel: string, ...args: any[]): Promise<T> {
export function invokeIpc<T>(channel: string, ...args: unknown[]): Promise<T> {
return ipcRenderer.invoke(channel, ...args);
}
@@ -42,6 +42,6 @@ export function invokeIpc<T>(channel: string, ...args: any[]): Promise<T> {
* @param channel - The IPC channel to send to
* @param args - Arguments to pass to the IPC handler
*/
export function sendIpc(channel: string, ...args: any[]): void {
export function sendIpc(channel: string, ...args: unknown[]): void {
ipcRenderer.send(channel, ...args);
}
+5 -10
View File
@@ -6,11 +6,6 @@ import type {
IPCResult,
InitializationResult,
AutoBuildVersionInfo,
ProjectContextData,
ProjectIndex,
GraphitiMemoryStatus,
ContextSearchResult,
MemoryEpisode,
ProjectEnvConfig,
ClaudeAuthResult,
InfrastructureStatus,
@@ -33,11 +28,11 @@ export interface ProjectAPI {
checkProjectVersion: (projectId: string) => Promise<IPCResult<AutoBuildVersionInfo>>;
// Context Operations
getProjectContext: (projectId: string) => Promise<any>;
refreshProjectIndex: (projectId: string) => Promise<any>;
getMemoryStatus: (projectId: string) => Promise<any>;
searchMemories: (projectId: string, query: string) => Promise<any>;
getRecentMemories: (projectId: string, limit?: number) => Promise<any>;
getProjectContext: (projectId: string) => Promise<IPCResult<unknown>>;
refreshProjectIndex: (projectId: string) => Promise<IPCResult<unknown>>;
getMemoryStatus: (projectId: string) => Promise<IPCResult<unknown>>;
searchMemories: (projectId: string, query: string) => Promise<IPCResult<unknown>>;
getRecentMemories: (projectId: string, limit?: number) => Promise<IPCResult<unknown>>;
// Environment Configuration
getProjectEnv: (projectId: string) => Promise<IPCResult<ProjectEnvConfig>>;
+13 -4
View File
@@ -5,9 +5,18 @@ import type {
TerminalCreateOptions,
RateLimitInfo,
ClaudeProfile,
ClaudeProfileSettings
ClaudeProfileSettings,
ClaudeUsageSnapshot
} from '../../shared/types';
/** Type for proactive swap notification events */
interface ProactiveSwapNotification {
fromProfile: { id: string; name: string };
toProfile: { id: string; name: string };
reason: string;
usageSnapshot: ClaudeUsageSnapshot;
}
export interface TerminalAPI {
// Terminal Operations
createTerminal: (options: TerminalCreateOptions) => Promise<IPCResult>;
@@ -67,7 +76,7 @@ export interface TerminalAPI {
// Usage Monitoring (Proactive Account Switching)
requestUsageUpdate: () => Promise<IPCResult<import('../../shared/types').ClaudeUsageSnapshot | null>>;
onUsageUpdated: (callback: (usage: import('../../shared/types').ClaudeUsageSnapshot) => void) => () => void;
onProactiveSwapNotification: (callback: (notification: any) => void) => () => void;
onProactiveSwapNotification: (callback: (notification: ProactiveSwapNotification) => void) => () => void;
}
export const createTerminalAPI = (): TerminalAPI => ({
@@ -294,9 +303,9 @@ export const createTerminalAPI = (): TerminalAPI => ({
},
onProactiveSwapNotification: (
callback: (notification: any) => void
callback: (notification: ProactiveSwapNotification) => void
): (() => void) => {
const handler = (_event: Electron.IpcRendererEvent, notification: any): void => {
const handler = (_event: Electron.IpcRendererEvent, notification: ProactiveSwapNotification): void => {
callback(notification);
};
ipcRenderer.on(IPC_CHANNELS.PROACTIVE_SWAP_NOTIFICATION, handler);
+2 -2
View File
@@ -118,7 +118,7 @@ export function App() {
useEffect(() => {
// When an update is downloaded and ready to install, open settings to updates section
const cleanupDownloaded = window.electronAPI.onAppUpdateDownloaded(() => {
console.log('[App] Update downloaded, opening settings to updates section');
console.warn('[App] Update downloaded, opening settings to updates section');
setSettingsInitialSection('updates');
setIsSettingsDialogOpen(true);
});
@@ -206,7 +206,7 @@ export function App() {
setSelectedTask(updatedTask);
}
}
}, [tasks, selectedTask?.id, selectedTask?.specId]);
}, [tasks, selectedTask?.id, selectedTask?.specId, selectedTask]);
const handleTaskClick = (task: Task) => {
setSelectedTask(task);
@@ -6,7 +6,7 @@
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { useTaskStore, persistUpdateTask } from '../stores/task-store';
import type { Task, TaskStatus, ElectronAPI } from '../../shared/types';
import type { Task, TaskStatus } from '../../shared/types';
// Helper to create test tasks
function createTestTask(overrides: Partial<Task> = {}): Task {
@@ -43,9 +43,7 @@ import {
} from './ui/select';
import { useRoadmapStore } from '../stores/roadmap-store';
import {
ROADMAP_PRIORITY_LABELS,
ROADMAP_COMPLEXITY_COLORS,
ROADMAP_IMPACT_COLORS
ROADMAP_PRIORITY_LABELS
} from '../../shared/constants';
import type {
RoadmapPhase,
@@ -3,8 +3,7 @@ import {
Download,
RefreshCw,
CheckCircle2,
AlertCircle,
ExternalLink
AlertCircle
} from 'lucide-react';
import { Button } from './ui/button';
import { Progress } from './ui/progress';
@@ -18,7 +17,6 @@ import {
} from './ui/dialog';
import type {
AppUpdateAvailableEvent,
AppUpdateDownloadedEvent,
AppUpdateProgress
} from '../../shared/types';
@@ -87,7 +85,7 @@ export function AppUpdateNotification() {
// Listen for update downloaded event
useEffect(() => {
const cleanup = window.electronAPI.onAppUpdateDownloaded((info) => {
const cleanup = window.electronAPI.onAppUpdateDownloaded((_info) => {
setIsDownloading(false);
setIsDownloaded(true);
setDownloadProgress(null);
@@ -44,7 +44,7 @@ export function EnvConfigModal({
}: EnvConfigModalProps) {
const [token, setToken] = useState('');
const [showToken, setShowToken] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [_isLoading, _setIsLoading] = useState(false);
const [isChecking, setIsChecking] = useState(true);
const [isSaving, setIsSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -33,7 +33,7 @@ export function GitSetupModal({
const [step, setStep] = useState<'info' | 'initializing' | 'success'>('info');
const needsGitInit = gitStatus && !gitStatus.isGitRepo;
const needsCommit = gitStatus && gitStatus.isGitRepo && !gitStatus.hasCommits;
const _needsCommit = gitStatus && gitStatus.isGitRepo && !gitStatus.hasCommits;
const handleInitializeGit = async () => {
if (!project) return;
@@ -12,8 +12,7 @@ import { Button } from './ui/button';
interface SwapNotification {
fromProfile: string;
toProfile: string;
reason: 'proactive' | 'reactive';
limitType: 'session' | 'weekly';
reason: string;
timestamp: Date;
}
@@ -22,12 +21,11 @@ export function ProactiveSwapListener() {
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const unsubscribe = window.electronAPI.onProactiveSwapNotification((data: any) => {
const unsubscribe = window.electronAPI.onProactiveSwapNotification((data) => {
const notif: SwapNotification = {
fromProfile: data.fromProfile,
toProfile: data.toProfile,
fromProfile: data.fromProfile.name,
toProfile: data.toProfile.name,
reason: data.reason,
limitType: data.limitType,
timestamp: new Date()
};
@@ -69,7 +67,7 @@ export function ProactiveSwapListener() {
<strong>{notification.toProfile}</strong>
<br />
<span className="text-[10px]">
({notification.limitType} usage threshold reached)
({notification.reason} swap)
</span>
</p>
</div>
@@ -53,7 +53,7 @@ export function ProjectSettings({ project, open, onOpenChange }: ProjectSettings
updateEnvConfig,
isLoadingEnv,
envError,
setEnvError,
setEnvError: _setEnvError,
isSavingEnv,
} = useEnvironmentConfig(project.id, project.autoBuildPath, open);
@@ -313,7 +313,7 @@ export function ProjectSettings({ project, open, onOpenChange }: ProjectSettings
onOpenChange={setShowLinearImportModal}
onImportComplete={(result) => {
// Optionally refresh or notify
console.log('Import complete:', result);
console.warn('Import complete:', result);
}}
/>
</Dialog>
@@ -108,7 +108,7 @@ export function Sidebar({
const [showGitSetupModal, setShowGitSetupModal] = useState(false);
const [gitStatus, setGitStatus] = useState<GitStatus | null>(null);
const [pendingProject, setPendingProject] = useState<Project | null>(null);
const [versionInfo, setVersionInfo] = useState<AutoBuildVersionInfo | null>(null);
const [_versionInfo, setVersionInfo] = useState<AutoBuildVersionInfo | null>(null);
const [isInitializing, setIsInitializing] = useState(false);
const selectedProject = projects.find((p) => p.id === selectedProjectId);
@@ -216,7 +216,7 @@ export function Sidebar({
setPendingProject(null);
};
const handleUpdate = async () => {
const _handleUpdate = async () => {
if (!selectedProjectId) return;
setIsInitializing(true);
@@ -231,7 +231,7 @@ export function Sidebar({
}
};
const handleSkipUpdate = () => {
const _handleSkipUpdate = () => {
setShowUpdateDialog(false);
setVersionInfo(null);
};
@@ -250,7 +250,7 @@ export function Sidebar({
}
};
const handleRemoveProject = async (projectId: string, e: React.MouseEvent) => {
const _handleRemoveProject = async (projectId: string, e: React.MouseEvent) => {
e.stopPropagation();
e.preventDefault();
await removeProject(projectId);
@@ -48,7 +48,7 @@ export function Terminal({
// Initialize xterm with command tracking
const {
terminalRef,
xtermRef,
xtermRef: _xtermRef,
write,
writeln,
focus,
@@ -93,10 +93,10 @@ export function TerminalGrid({ projectPath, onNewTaskClick }: TerminalGridProps)
const sessionsResult = await window.electronAPI.getTerminalSessionsForDate(date, projectPath);
const sessionsToRestore = sessionsResult.success ? sessionsResult.data || [] : [];
console.log(`[TerminalGrid] Found ${sessionsToRestore.length} sessions to restore from ${date}`);
console.warn(`[TerminalGrid] Found ${sessionsToRestore.length} sessions to restore from ${date}`);
if (sessionsToRestore.length === 0) {
console.log('[TerminalGrid] No sessions found for this date');
console.warn('[TerminalGrid] No sessions found for this date');
setIsRestoring(false);
return;
}
@@ -119,7 +119,7 @@ export function TerminalGrid({ projectPath, onNewTaskClick }: TerminalGridProps)
);
if (result.success && result.data) {
console.log(`[TerminalGrid] Main process restored ${result.data.restored} sessions from ${date}`);
console.warn(`[TerminalGrid] Main process restored ${result.data.restored} sessions from ${date}`);
// Add each successfully restored session to the renderer's terminal store
for (const sessionResult of result.data.sessions) {
@@ -127,7 +127,7 @@ export function TerminalGrid({ projectPath, onNewTaskClick }: TerminalGridProps)
// Find the full session data
const fullSession = sessionsToRestore.find(s => s.id === sessionResult.id);
if (fullSession) {
console.log(`[TerminalGrid] Adding restored terminal to store: ${fullSession.id}`);
console.warn(`[TerminalGrid] Adding restored terminal to store: ${fullSession.id}`);
addRestoredTerminal(fullSession);
}
}
@@ -162,6 +162,11 @@ export function TerminalGrid({ projectPath, onNewTaskClick }: TerminalGridProps)
isDirectory: boolean;
} | null>(null);
const handleCloseTerminal = useCallback((id: string) => {
window.electronAPI.destroyTerminal(id);
removeTerminal(id);
}, [removeTerminal]);
// Handle keyboard shortcut for new terminal
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
@@ -181,12 +186,7 @@ export function TerminalGrid({ projectPath, onNewTaskClick }: TerminalGridProps)
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [addTerminal, canAddTerminal, projectPath, activeTerminalId]);
const handleCloseTerminal = useCallback((id: string) => {
window.electronAPI.destroyTerminal(id);
removeTerminal(id);
}, [removeTerminal]);
}, [addTerminal, canAddTerminal, projectPath, activeTerminalId, handleCloseTerminal]);
const handleAddTerminal = useCallback(() => {
if (canAddTerminal()) {
@@ -83,7 +83,7 @@ export function WelcomeScreen({
<Separator />
<ScrollArea className="max-h-[320px]">
<div className="p-2">
{recentProjects.map((project, index) => (
{recentProjects.map((project, _index) => (
<button
key={project.id}
onClick={() => onSelectProject(project.id)}
@@ -11,7 +11,6 @@ import {
Plus,
Minus,
ChevronRight,
Terminal,
Check,
X
} from 'lucide-react';
@@ -27,13 +26,6 @@ import {
DialogHeader,
DialogTitle
} from './ui/dialog';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from './ui/select';
import {
AlertDialog,
AlertDialogAction,
@@ -46,7 +38,7 @@ import {
} from './ui/alert-dialog';
import { useProjectStore } from '../stores/project-store';
import { useTaskStore } from '../stores/task-store';
import type { WorktreeListItem, WorktreeMergeResult, WorktreeDiscardResult } from '../../shared/types';
import type { WorktreeListItem, WorktreeMergeResult } from '../../shared/types';
interface WorktreesProps {
projectId: string;
@@ -2,7 +2,7 @@
* Unit tests for RoadmapGenerationProgress component
* Tests phase rendering, configuration, error display, and animation logic
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { describe, it, expect } from 'vitest';
import type { RoadmapGenerationStatus } from '../../../shared/types/roadmap';
// Helper to create test generation status
@@ -426,7 +426,7 @@ describe('RoadmapGenerationProgress', () => {
const currentPhase = 'complete';
// All step phases should show as complete when phase is 'complete'
stepPhases.forEach(phase => {
stepPhases.forEach(_phase => {
// The getPhaseState function returns 'complete' for all phases when currentPhase is 'complete'
expect(currentPhase === 'complete').toBe(true);
});
@@ -16,7 +16,7 @@ export function Changelog() {
sourceMode,
branches,
tags,
currentBranch,
currentBranch: _currentBranch,
defaultBranch,
previewCommits,
isLoadingGitData,
@@ -118,19 +118,7 @@ export function useChangelog() {
if (selectedProjectId && (sourceMode === 'git-history' || sourceMode === 'branch-diff')) {
loadCommitsPreview(selectedProjectId);
}
}, [
selectedProjectId,
sourceMode,
gitHistoryType,
gitHistoryCount,
gitHistorySinceDate,
gitHistoryFromTag,
gitHistoryToTag,
gitHistorySinceVersion,
includeMergeCommits,
baseBranch,
compareBranch
]);
}, [selectedProjectId, sourceMode]);
// Set up event listeners for generation
useEffect(() => {
@@ -180,7 +168,7 @@ export function useChangelog() {
cleanupComplete();
cleanupError();
};
}, [selectedProjectId]);
}, [selectedProjectId, setError, setGenerationProgress, setIsGenerating, updateGeneratedChangelog]);
const handleGenerate = () => {
if (selectedProjectId) {
@@ -58,7 +58,7 @@ export function useImageUpload({ projectId, content, onContentChange }: UseImage
} else {
setImageError(result.error || 'Failed to save image');
}
} catch (err) {
} catch (_err) {
setImageError('Failed to process image');
}
}, [projectId, insertImageAtCursor]);
@@ -66,7 +66,7 @@ export function GenerationProgressScreen({
};
// Count how many types are still generating
const generatingCount = enabledTypes.filter((t) => typeStates[t] === 'generating').length;
const _generatingCount = enabledTypes.filter((t) => typeStates[t] === 'generating').length;
const completedCount = enabledTypes.filter((t) => typeStates[t] === 'completed').length;
return (
@@ -1,4 +1,4 @@
import { ExternalLink, Play, X, Check } from 'lucide-react';
import { ExternalLink, Play, X } from 'lucide-react';
import { Button } from '../ui/button';
import { Badge } from '../ui/badge';
import { Card } from '../ui/card';
@@ -13,7 +13,6 @@ import {
SECURITY_SEVERITY_COLORS,
UIUX_CATEGORY_LABELS,
DOCUMENTATION_CATEGORY_LABELS,
SECURITY_CATEGORY_LABELS,
CODE_QUALITY_SEVERITY_COLORS
} from '../../../shared/constants';
import type {
@@ -1,6 +1,5 @@
import { Zap, Palette, BookOpen, Shield, Gauge } from 'lucide-react';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../ui/tabs';
import type { IdeationType } from '../../../shared/types';
import { Tabs, TabsList, TabsTrigger } from '../ui/tabs';
interface IdeationFiltersProps {
activeTab: string;
@@ -1,4 +1,4 @@
import { Lightbulb, Eye, EyeOff, Settings2, Plus, Trash2, RefreshCw, Archive, CheckSquare, Square, X } from 'lucide-react';
import { Lightbulb, Eye, EyeOff, Settings2, Plus, Trash2, RefreshCw, Archive, CheckSquare, X } from 'lucide-react';
import { Button } from '../ui/button';
import { Badge } from '../ui/badge';
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
@@ -2,7 +2,7 @@
* Hook for loading Linear issues for a selected team/project
*/
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
import type { LinearIssue } from '../types';
export function useLinearIssues(
@@ -15,6 +15,10 @@ export function useLinearIssues(
const [isLoadingIssues, setIsLoadingIssues] = useState(false);
const [error, setError] = useState<string | null>(null);
// Use ref to store the callback to avoid unnecessary re-renders
const onIssuesChangeRef = useRef(onIssuesChange);
onIssuesChangeRef.current = onIssuesChange;
useEffect(() => {
const loadIssues = async () => {
if (!selectedTeamId) {
@@ -33,7 +37,7 @@ export function useLinearIssues(
);
if (result.success && result.data) {
setIssues(result.data);
onIssuesChange?.();
onIssuesChangeRef.current?.();
} else {
setError(result.error || 'Failed to load issues');
}
@@ -17,7 +17,7 @@ export function AutoBuildIntegration({
isCheckingVersion,
isUpdating,
onInitialize,
onUpdate,
onUpdate: _onUpdate,
}: AutoBuildIntegrationProps) {
return (
<section className="space-y-4">
@@ -42,7 +42,7 @@ export function GeneralSettings({
isCheckingVersion,
isUpdating,
handleInitialize,
handleUpdate
handleUpdate: _handleUpdate
}: GeneralSettingsProps) {
return (
<>
@@ -22,9 +22,9 @@ const DEBUG = process.env.NODE_ENV === 'development' || process.env.DEBUG === 't
function debugLog(message: string, data?: unknown) {
if (DEBUG) {
if (data !== undefined) {
console.log(`[GitHubOAuth] ${message}`, data);
console.warn(`[GitHubOAuth] ${message}`, data);
} else {
console.log(`[GitHubOAuth] ${message}`);
console.warn(`[GitHubOAuth] ${message}`);
}
}
}
@@ -36,7 +36,7 @@ function debugLog(message: string, data?: unknown) {
export function GitHubOAuthFlow({ onSuccess, onCancel }: GitHubOAuthFlowProps) {
const [status, setStatus] = useState<'checking' | 'need-install' | 'need-auth' | 'authenticating' | 'success' | 'error'>('checking');
const [error, setError] = useState<string | null>(null);
const [cliInstalled, setCliInstalled] = useState(false);
const [_cliInstalled, setCliInstalled] = useState(false);
const [cliVersion, setCliVersion] = useState<string | undefined>();
const [username, setUsername] = useState<string | undefined>();
@@ -52,6 +52,7 @@ export function GitHubOAuthFlow({ onSuccess, onCancel }: GitHubOAuthFlowProps) {
hasCheckedRef.current = true;
debugLog('Component mounted, checking GitHub status...');
checkGitHubStatus();
// eslint-disable-next-line react-hooks/exhaustive-deps -- Only run once on mount, checkGitHubStatus is intentionally excluded
}, []);
const checkGitHubStatus = async () => {
@@ -84,6 +84,7 @@ export function IntegrationSettings({
if (githubExpanded && project.path) {
loadBranches();
}
// eslint-disable-next-line react-hooks/exhaustive-deps -- loadBranches is intentionally excluded to avoid infinite loops
}, [githubExpanded, project.path]);
const loadBranches = async () => {
@@ -189,7 +189,7 @@ export function ProjectSettings({ project, open, onOpenChange }: ProjectSettings
open={showLinearImportModal}
onOpenChange={setShowLinearImportModal}
onImportComplete={(result) => {
console.log('Import complete:', result);
console.warn('Import complete:', result);
}}
/>
</Dialog>
@@ -9,7 +9,7 @@ import type { PhaseCardProps } from './types';
export function PhaseCard({
phase,
features,
isFirst,
isFirst: _isFirst,
onFeatureSelect,
onConvertToSpec,
onGoToTask,
@@ -29,7 +29,7 @@ import type {
*/
function ReleaseNotesRenderer({ markdown }: { markdown: string }) {
const html = useMemo(() => {
let result = markdown
const result = markdown
// Escape HTML
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
@@ -48,12 +48,12 @@ function ReleaseNotesRenderer({ markdown }: { markdown: string }) {
// Line breaks for remaining lines
.replace(/\n\n/g, '<div class="h-2"></div>')
.replace(/\n/g, '<br/>');
return result;
}, [markdown]);
return (
<div
<div
className="text-sm text-muted-foreground leading-relaxed"
dangerouslySetInnerHTML={{ __html: html }}
/>
@@ -79,7 +79,7 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
// Electron app update state
const [appUpdateInfo, setAppUpdateInfo] = useState<AppUpdateAvailableEvent | null>(null);
const [isCheckingAppUpdate, setIsCheckingAppUpdate] = useState(false);
const [_isCheckingAppUpdate, setIsCheckingAppUpdate] = useState(false);
const [isDownloadingAppUpdate, setIsDownloadingAppUpdate] = useState(false);
const [appDownloadProgress, setAppDownloadProgress] = useState<AppUpdateProgress | null>(null);
const [isAppUpdateDownloaded, setIsAppUpdateDownloaded] = useState(false);
@@ -329,7 +329,7 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version
<ReleaseNotesRenderer markdown={sourceUpdateCheck.releaseNotes} />
</div>
)}
{sourceUpdateCheck.releaseUrl && (
<button
onClick={() => window.electronAPI.openExternal(sourceUpdateCheck.releaseUrl!)}
@@ -65,7 +65,7 @@ function ProjectSettingsContentInner({
onHookReady: (hook: UseProjectSettingsReturn | null) => void;
}) {
const hook = useProjectSettings(project, isOpen);
// Keep a stable ref to the hook for the parent
const hookRef = useRef(hook);
hookRef.current = hook;
@@ -90,8 +90,8 @@ function ProjectSettingsContentInner({
setShowFalkorPassword,
showGitHubToken,
setShowGitHubToken,
expandedSections,
toggleSection,
expandedSections: _expandedSections,
toggleSection: _toggleSection,
gitHubConnectionStatus,
isCheckingGitHub,
isCheckingClaudeAuth,
@@ -162,7 +162,7 @@ function ProjectSettingsContentInner({
open={showLinearImportModal}
onOpenChange={setShowLinearImportModal}
onImportComplete={(result) => {
console.log('Import complete:', result);
console.warn('Import complete:', result);
}}
/>
</>
@@ -14,7 +14,7 @@ interface InitializationGuardProps {
export function InitializationGuard({
initialized,
title,
description,
description: _description,
children
}: InitializationGuardProps) {
if (!initialized) {
@@ -14,9 +14,9 @@ const DEBUG = process.env.NODE_ENV === 'development' || process.env.DEBUG === 't
function debugLog(message: string, data?: unknown) {
if (DEBUG) {
if (data !== undefined) {
console.log(`[GitHubIntegration] ${message}`, data);
console.warn(`[GitHubIntegration] ${message}`, data);
} else {
console.log(`[GitHubIntegration] ${message}`);
console.warn(`[GitHubIntegration] ${message}`);
}
}
}
@@ -43,8 +43,8 @@ interface GitHubIntegrationProps {
export function GitHubIntegration({
envConfig,
updateEnvConfig,
showGitHubToken,
setShowGitHubToken,
showGitHubToken: _showGitHubToken,
setShowGitHubToken: _setShowGitHubToken,
gitHubConnectionStatus,
isCheckingGitHub
}: GitHubIntegrationProps) {
@@ -23,7 +23,7 @@ interface TaskDetailPanelProps {
export function TaskDetailPanel({ task, onClose }: TaskDetailPanelProps) {
const state = useTaskDetail({ task });
const progress = calculateProgress(task.subtasks);
const _progress = calculateProgress(task.subtasks);
// Event Handlers
const handleStartStop = () => {
@@ -68,35 +68,35 @@ export function TaskDetailPanel({ task, onClose }: TaskDetailPanelProps) {
};
const handleMerge = async () => {
console.log('[TaskDetailPanel] handleMerge called, stageOnly:', state.stageOnly);
console.warn('[TaskDetailPanel] handleMerge called, stageOnly:', state.stageOnly);
state.setIsMerging(true);
state.setWorkspaceError(null);
try {
console.log('[TaskDetailPanel] Calling mergeWorktree...');
console.warn('[TaskDetailPanel] Calling mergeWorktree...');
const result = await window.electronAPI.mergeWorktree(task.id, { noCommit: state.stageOnly });
console.log('[TaskDetailPanel] mergeWorktree result:', JSON.stringify(result, null, 2));
console.warn('[TaskDetailPanel] mergeWorktree result:', JSON.stringify(result, null, 2));
if (result.success && result.data?.success) {
// For stage-only: don't close the panel, show success message
// For full merge: close the panel
if (state.stageOnly && result.data.staged) {
// Changes are staged in main project - show success but keep panel open
console.log('[TaskDetailPanel] Stage-only success, showing success message');
console.warn('[TaskDetailPanel] Stage-only success, showing success message');
state.setWorkspaceError(null);
state.setStagedSuccess(result.data.message || 'Changes staged in main project');
state.setStagedProjectPath(result.data.projectPath);
} else {
console.log('[TaskDetailPanel] Full merge success, closing panel');
console.warn('[TaskDetailPanel] Full merge success, closing panel');
onClose();
}
} else {
console.log('[TaskDetailPanel] Merge failed:', result.data?.message || result.error);
console.warn('[TaskDetailPanel] Merge failed:', result.data?.message || result.error);
state.setWorkspaceError(result.data?.message || result.error || 'Failed to merge changes');
}
} catch (error) {
console.error('[TaskDetailPanel] handleMerge exception:', error);
state.setWorkspaceError(error instanceof Error ? error.message : 'Unknown error during merge');
} finally {
console.log('[TaskDetailPanel] Setting isMerging to false');
console.warn('[TaskDetailPanel] Setting isMerging to false');
state.setIsMerging(false);
}
};

Some files were not shown because too many files have changed in this diff Show More