Skip to content

Commit d4b844b

Browse files
feat: added parallelization in checks:
Problem The codemie doctor command spawned 14-23 subprocesses sequentially. On Windows, process creation is ~10x slower than Unix, causing the command to exceed the 60s test timeout (took ~63.5s). Changes 3 parallelization layers: 1. src/agents/registry.ts — getInstalledAgents() now checks all 5 agents' isInstalled() in parallel via Promise.all() instead of a sequential loop. This was the single biggest bottleneck (4-6 process spawns serialized). 2. src/cli/commands/doctor/checks/AgentsCheck.ts — run() now fetches getVersion() + getInstallationMethod() for all installed agents in parallel instead of one at a time. 3. src/cli/commands/doctor/checks/FrameworksCheck.ts — run() now checks isInstalled() + getVersion() for all frameworks in parallel. 4. src/cli/commands/doctor/index.ts — Restructured the orchestrator into 3 parallel groups: - Group 1: Tool checks (Node, npm, Python, uv, AWS CLI) — all run concurrently - Group 2: AI Config + Provider check — sequential (provider depends on config) - Group 3: Discovery checks (Agents, Workflows, Frameworks) — all run concurrently Results are collected first, then displayed sequentially to preserve output ordering. Before: ~23 subprocess spawns executed one after another After: Same subprocess spawns but organized into parallel batches, reducing wall-clock time from O(n) to roughly O(max-per-group)
1 parent a5e3cf4 commit d4b844b

5 files changed

Lines changed: 129 additions & 126 deletions

File tree

src/agents/plugins/claude/claude.plugin.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,6 @@ import {
2626
* Supported Claude Code version
2727
* Latest version tested and verified with CodeMie backend
2828
*
29-
* Latest available versions can be found here:
30-
* https://github.com/anthropics/claude-code/releases
31-
*
3229
* **UPDATE THIS WHEN BUMPING CLAUDE VERSION**
3330
*/
3431
const CLAUDE_SUPPORTED_VERSION = '2.1.31';

src/agents/registry.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,16 @@ export class AgentRegistry {
6565

6666
static async getInstalledAgents(): Promise<AgentAdapter[]> {
6767
AgentRegistry.initialize();
68-
const agents: AgentAdapter[] = [];
69-
for (const adapter of AgentRegistry.adapters.values()) {
70-
if (await adapter.isInstalled()) {
71-
agents.push(adapter);
72-
}
73-
}
74-
return agents;
68+
const allAdapters = Array.from(AgentRegistry.adapters.values());
69+
const installResults = await Promise.all(
70+
allAdapters.map(async (adapter) => ({
71+
adapter,
72+
installed: await adapter.isInstalled()
73+
}))
74+
);
75+
return installResults
76+
.filter(({ installed }) => installed)
77+
.map(({ adapter }) => adapter);
7578
}
7679

7780
/**

src/cli/commands/doctor/checks/AgentsCheck.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,17 @@ export class AgentsCheck implements ItemWiseHealthCheck {
3636
const installedAgents = await AgentRegistry.getInstalledAgents();
3737

3838
if (installedAgents.length > 0) {
39-
for (const agent of installedAgents) {
40-
const version = await agent.getVersion();
41-
const versionStr = version ? ` (${version})` : '';
39+
// Parallelize version + installation method checks across all agents
40+
const agentResults = await Promise.all(
41+
installedAgents.map(async (agent) => {
42+
const version = await agent.getVersion();
43+
const versionStr = version ? ` (${version})` : '';
44+
const deprecationWarning = await this.checkDeprecatedInstallation(agent, versionStr);
45+
return { agent, versionStr, deprecationWarning };
46+
})
47+
);
4248

43-
// Check for deprecated npm installation
44-
const deprecationWarning = await this.checkDeprecatedInstallation(agent, versionStr);
49+
for (const { agent, versionStr, deprecationWarning } of agentResults) {
4550
if (deprecationWarning) {
4651
details.push(deprecationWarning);
4752
continue;

src/cli/commands/doctor/checks/FrameworksCheck.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,16 @@ export class FrameworksCheck implements ItemWiseHealthCheck {
2323
message: 'No frameworks registered'
2424
});
2525
} else {
26-
// Check each framework
27-
for (const framework of frameworks) {
28-
const installed = await framework.isInstalled();
29-
const version = installed ? await framework.getVersion() : null;
26+
// Check all frameworks in parallel
27+
const frameworkResults = await Promise.all(
28+
frameworks.map(async (framework) => {
29+
const installed = await framework.isInstalled();
30+
const version = installed ? await framework.getVersion() : null;
31+
return { framework, installed, version };
32+
})
33+
);
34+
35+
for (const { framework, installed, version } of frameworkResults) {
3036
const versionStr = version ? ` (${version})` : '';
3137

3238
if (installed) {

src/cli/commands/doctor/index.ts

Lines changed: 99 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import { Command } from 'commander';
66
import chalk from 'chalk';
77
import os from 'os';
8-
import { HealthCheck, ItemWiseHealthCheck, HealthCheckResult } from './types.js';
8+
import { HealthCheckResult } from './types.js';
99
import { HealthCheckFormatter } from './formatter.js';
1010
import {
1111
NodeVersionCheck,
@@ -79,120 +79,112 @@ export function createDoctorCommand(): Command {
7979
// Display header
8080
formatter.displayHeader();
8181

82-
// Define standard health checks
83-
const checks: HealthCheck[] = [
84-
new NodeVersionCheck(),
85-
new NpmCheck(),
86-
new PythonCheck(),
87-
new UvCheck(),
88-
new AwsCliCheck(),
89-
new AIConfigCheck(),
90-
new AgentsCheck(),
91-
new WorkflowsCheck(),
92-
new FrameworksCheck()
93-
];
94-
95-
// Run and display standard checks immediately
96-
for (const check of checks) {
97-
logger.debug(`=== Running Check: ${check.name} ===`);
98-
const startTime = Date.now();
99-
100-
// Check if this is an ItemWiseHealthCheck
101-
const isItemWise = 'runWithItemDisplay' in check;
102-
103-
if (isItemWise) {
104-
// Display section header
105-
console.log(formatter['getCheckHeader'](check.name));
106-
107-
// Run with item-by-item display
108-
const result = await (check as ItemWiseHealthCheck).runWithItemDisplay(
109-
(itemName) => {
110-
logger.debug(` Checking item: ${itemName}`);
111-
formatter.startItem(itemName);
112-
},
113-
(detail) => {
114-
logger.debug(` Result: ${detail.status} - ${detail.message}`);
115-
formatter.displayItem(detail);
116-
}
117-
);
118-
results.push(result);
119-
120-
const elapsed = Date.now() - startTime;
121-
logger.debug(`Check completed in ${elapsed}ms: ${result.success ? 'SUCCESS' : 'FAILED'}`);
122-
logger.debug('');
123-
124-
// Add blank line after section
125-
console.log();
126-
} else {
127-
// Regular check with section-level progress
128-
formatter.startCheck(check.name);
129-
const result = await check.run((message) => {
130-
logger.debug(` Progress: ${message}`);
131-
formatter.updateProgress(message);
82+
// Helper to display a pre-computed check result
83+
const displayResult = (result: HealthCheckResult): void => {
84+
formatter.displayCheck(result);
85+
if (result.details && result.details.length > 0) {
86+
result.details.forEach(detail => {
87+
logger.debug(` - ${detail.status}: ${detail.message}`);
13288
});
133-
results.push(result);
134-
formatter.displayCheck(result);
135-
136-
const elapsed = Date.now() - startTime;
137-
logger.debug(`Check completed in ${elapsed}ms: ${result.success ? 'SUCCESS' : 'FAILED'}`);
138-
if (result.details && result.details.length > 0) {
139-
result.details.forEach(detail => {
140-
logger.debug(` - ${detail.status}: ${detail.message}`);
141-
});
142-
}
143-
logger.debug('');
14489
}
90+
logger.debug('');
91+
};
92+
93+
// --- Group 1: Independent tool checks (run in parallel) ---
94+
const nodeCheck = new NodeVersionCheck();
95+
const npmCheck = new NpmCheck();
96+
const pythonCheck = new PythonCheck();
97+
const uvCheck = new UvCheck();
98+
const awsCheck = new AwsCliCheck();
99+
100+
logger.debug('=== Running Tool Checks (parallel) ===');
101+
const toolStartTime = Date.now();
102+
const [nodeResult, npmResult, pythonResult, uvResult, awsResult] = await Promise.all([
103+
nodeCheck.run(),
104+
npmCheck.run(),
105+
pythonCheck.run(),
106+
uvCheck.run(),
107+
awsCheck.run()
108+
]);
109+
logger.debug(`Tool checks completed in ${Date.now() - toolStartTime}ms`);
110+
111+
// Display tool check results sequentially
112+
for (const result of [nodeResult, npmResult, pythonResult, uvResult, awsResult]) {
113+
results.push(result);
114+
displayResult(result);
115+
}
145116

146-
// After AIConfigCheck, immediately run provider-specific checks
147-
if (check instanceof AIConfigCheck) {
148-
const config = check.getConfig();
149-
150-
if (config && config.provider) {
151-
logger.debug(`=== Running Provider Check: ${config.provider} ===`);
152-
logger.debug(`Base URL: ${config.baseUrl}`);
153-
logger.debug(`Model: ${config.model}`);
154-
155-
// Get health check from ProviderRegistry
156-
const healthCheck = ProviderRegistry.getHealthCheck(config.provider);
157-
158-
if (healthCheck) {
159-
formatter.startCheck('Provider');
160-
161-
try {
162-
const providerStartTime = Date.now();
163-
const providerResult = await healthCheck.check(config);
164-
const elapsed = Date.now() - providerStartTime;
165-
166-
logger.debug(`Provider check completed in ${elapsed}ms`);
167-
logger.debug(`Status: ${providerResult.status}`);
168-
169-
const doctorResult = adaptProviderResult(providerResult);
170-
results.push(doctorResult);
171-
formatter.displayCheckWithHeader(doctorResult);
172-
} catch (error) {
173-
const errorMessage = error instanceof Error ? error.message : String(error);
174-
logger.error(`Provider check failed: ${errorMessage}`);
175-
if (error instanceof Error && error.stack) {
176-
logger.debug(`Stack trace: ${error.stack}`);
177-
}
178-
179-
// If check throws, capture error
180-
results.push({
181-
name: 'Provider Check Error',
182-
success: false,
183-
details: [{
184-
status: 'error',
185-
message: `Check failed: ${errorMessage}`
186-
}]
187-
});
188-
}
189-
} else {
190-
logger.debug(`No health check available for provider: ${config.provider}`);
117+
// --- Group 2: AI Config + Provider check (sequential, provider depends on config) ---
118+
const aiConfigCheck = new AIConfigCheck();
119+
logger.debug('=== Running Check: Active Profile ===');
120+
const configStartTime = Date.now();
121+
const configResult = await aiConfigCheck.run();
122+
logger.debug(`Check completed in ${Date.now() - configStartTime}ms`);
123+
results.push(configResult);
124+
displayResult(configResult);
125+
126+
// Run provider-specific checks if config is available
127+
const config = aiConfigCheck.getConfig();
128+
if (config && config.provider) {
129+
logger.debug(`=== Running Provider Check: ${config.provider} ===`);
130+
logger.debug(`Base URL: ${config.baseUrl}`);
131+
logger.debug(`Model: ${config.model}`);
132+
133+
const healthCheck = ProviderRegistry.getHealthCheck(config.provider);
134+
135+
if (healthCheck) {
136+
formatter.startCheck('Provider');
137+
138+
try {
139+
const providerStartTime = Date.now();
140+
const providerResult = await healthCheck.check(config);
141+
logger.debug(`Provider check completed in ${Date.now() - providerStartTime}ms`);
142+
logger.debug(`Status: ${providerResult.status}`);
143+
144+
const doctorResult = adaptProviderResult(providerResult);
145+
results.push(doctorResult);
146+
formatter.displayCheckWithHeader(doctorResult);
147+
} catch (error) {
148+
const errorMessage = error instanceof Error ? error.message : String(error);
149+
logger.error(`Provider check failed: ${errorMessage}`);
150+
if (error instanceof Error && error.stack) {
151+
logger.debug(`Stack trace: ${error.stack}`);
191152
}
153+
154+
results.push({
155+
name: 'Provider Check Error',
156+
success: false,
157+
details: [{
158+
status: 'error',
159+
message: `Check failed: ${errorMessage}`
160+
}]
161+
});
192162
}
163+
} else {
164+
logger.debug(`No health check available for provider: ${config.provider}`);
193165
}
194166
}
195167

168+
// --- Group 3: Discovery checks (run in parallel) ---
169+
const agentsCheck = new AgentsCheck();
170+
const workflowsCheck = new WorkflowsCheck();
171+
const frameworksCheck = new FrameworksCheck();
172+
173+
logger.debug('=== Running Discovery Checks (parallel) ===');
174+
const discoveryStartTime = Date.now();
175+
const [agentsResult, workflowsResult, frameworksResult] = await Promise.all([
176+
agentsCheck.run(),
177+
workflowsCheck.run(),
178+
frameworksCheck.run()
179+
]);
180+
logger.debug(`Discovery checks completed in ${Date.now() - discoveryStartTime}ms`);
181+
182+
// Display discovery check results sequentially
183+
for (const result of [agentsResult, workflowsResult, frameworksResult]) {
184+
results.push(result);
185+
displayResult(result);
186+
}
187+
196188
logger.debug('=== All Checks Completed ===');
197189
const successCount = results.filter(r => r.success).length;
198190
logger.debug(`Passed: ${successCount}/${results.length}`);

0 commit comments

Comments
 (0)