smart-coding-mcp 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -2
- package/config.json +4 -2
- package/features/get-status.js +37 -6
- package/features/hybrid-search.js +23 -4
- package/features/index-codebase.js +137 -60
- package/index.js +141 -72
- package/lib/config.js +52 -2
- package/lib/resource-throttle.js +85 -0
- package/lib/sqlite-cache.js +408 -0
- package/package.json +2 -1
- package/test/better-sqlite3.test.js +153 -0
package/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
CallToolRequestSchema,
|
|
6
|
+
ListToolsRequestSchema,
|
|
7
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
5
8
|
import fs from "fs/promises";
|
|
6
9
|
import { createRequire } from "module";
|
|
7
10
|
|
|
@@ -10,7 +13,7 @@ const require = createRequire(import.meta.url);
|
|
|
10
13
|
const packageJson = require("./package.json");
|
|
11
14
|
|
|
12
15
|
import { loadConfig } from "./lib/config.js";
|
|
13
|
-
import {
|
|
16
|
+
import { SQLiteCache } from "./lib/sqlite-cache.js";
|
|
14
17
|
import { createEmbedder } from "./lib/mrl-embedder.js";
|
|
15
18
|
import { CodebaseIndexer } from "./features/index-codebase.js";
|
|
16
19
|
import { HybridSearch } from "./features/hybrid-search.js";
|
|
@@ -24,24 +27,30 @@ import * as GetStatusFeature from "./features/get-status.js";
|
|
|
24
27
|
|
|
25
28
|
// Parse workspace from command line arguments
|
|
26
29
|
const args = process.argv.slice(2);
|
|
27
|
-
const workspaceIndex = args.findIndex(arg => arg.startsWith(
|
|
30
|
+
const workspaceIndex = args.findIndex((arg) => arg.startsWith("--workspace"));
|
|
28
31
|
let workspaceDir = process.cwd(); // Default to current directory
|
|
29
32
|
|
|
30
33
|
if (workspaceIndex !== -1) {
|
|
31
34
|
const arg = args[workspaceIndex];
|
|
32
35
|
let rawWorkspace = null;
|
|
33
36
|
|
|
34
|
-
if (arg.includes(
|
|
35
|
-
rawWorkspace = arg.split(
|
|
37
|
+
if (arg.includes("=")) {
|
|
38
|
+
rawWorkspace = arg.split("=")[1];
|
|
36
39
|
} else if (workspaceIndex + 1 < args.length) {
|
|
37
40
|
rawWorkspace = args[workspaceIndex + 1];
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
// Check if IDE variable wasn't expanded (contains ${})
|
|
41
|
-
if (rawWorkspace && rawWorkspace.includes(
|
|
42
|
-
console.error(
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
if (rawWorkspace && rawWorkspace.includes("${")) {
|
|
45
|
+
console.error(
|
|
46
|
+
`[Server] FATAL: Workspace variable "${rawWorkspace}" was not expanded by your IDE.`
|
|
47
|
+
);
|
|
48
|
+
console.error(
|
|
49
|
+
`[Server] This typically means your MCP client does not support dynamic variables.`
|
|
50
|
+
);
|
|
51
|
+
console.error(
|
|
52
|
+
`[Server] Please use an absolute path instead: --workspace /path/to/your/project`
|
|
53
|
+
);
|
|
45
54
|
process.exit(1);
|
|
46
55
|
} else if (rawWorkspace) {
|
|
47
56
|
workspaceDir = rawWorkspace;
|
|
@@ -58,110 +67,167 @@ let cache = null;
|
|
|
58
67
|
let indexer = null;
|
|
59
68
|
let hybridSearch = null;
|
|
60
69
|
let config = null;
|
|
70
|
+
let isInitialized = false;
|
|
71
|
+
let initializationPromise = null;
|
|
61
72
|
|
|
62
73
|
// Feature registry - ordered by priority (semantic_search first as primary tool)
|
|
63
74
|
const features = [
|
|
64
75
|
{
|
|
65
76
|
module: HybridSearchFeature,
|
|
66
77
|
instance: null,
|
|
67
|
-
handler: HybridSearchFeature.handleToolCall
|
|
78
|
+
handler: HybridSearchFeature.handleToolCall,
|
|
68
79
|
},
|
|
69
80
|
{
|
|
70
81
|
module: IndexCodebaseFeature,
|
|
71
82
|
instance: null,
|
|
72
|
-
handler: IndexCodebaseFeature.handleToolCall
|
|
83
|
+
handler: IndexCodebaseFeature.handleToolCall,
|
|
73
84
|
},
|
|
74
85
|
{
|
|
75
86
|
module: ClearCacheFeature,
|
|
76
87
|
instance: null,
|
|
77
|
-
handler: ClearCacheFeature.handleToolCall
|
|
88
|
+
handler: ClearCacheFeature.handleToolCall,
|
|
78
89
|
},
|
|
79
90
|
{
|
|
80
91
|
module: CheckLastVersionFeature,
|
|
81
92
|
instance: null,
|
|
82
|
-
handler: CheckLastVersionFeature.handleToolCall
|
|
93
|
+
handler: CheckLastVersionFeature.handleToolCall,
|
|
83
94
|
},
|
|
84
95
|
{
|
|
85
96
|
module: SetWorkspaceFeature,
|
|
86
97
|
instance: null,
|
|
87
|
-
handler: SetWorkspaceFeature.handleToolCall
|
|
98
|
+
handler: SetWorkspaceFeature.handleToolCall,
|
|
88
99
|
},
|
|
89
100
|
{
|
|
90
101
|
module: GetStatusFeature,
|
|
91
102
|
instance: null,
|
|
92
|
-
handler: GetStatusFeature.handleToolCall
|
|
93
|
-
}
|
|
103
|
+
handler: GetStatusFeature.handleToolCall,
|
|
104
|
+
},
|
|
94
105
|
];
|
|
95
106
|
|
|
96
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Lazy initialization - only loads heavy resources when first needed
|
|
109
|
+
* This prevents IDE blocking on startup
|
|
110
|
+
*/
|
|
111
|
+
async function ensureInitialized() {
|
|
112
|
+
// Already initialized
|
|
113
|
+
if (isInitialized) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Initialization in progress, wait for it
|
|
118
|
+
if (initializationPromise) {
|
|
119
|
+
return initializationPromise;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Start initialization
|
|
123
|
+
initializationPromise = (async () => {
|
|
124
|
+
console.error("[Server] Loading AI model and cache (first use)...");
|
|
125
|
+
|
|
126
|
+
// Load AI model using MRL embedder factory
|
|
127
|
+
embedder = await createEmbedder(config);
|
|
128
|
+
console.error(
|
|
129
|
+
`[Server] Model: ${embedder.modelName} (${embedder.dimension}d, device: ${embedder.device})`
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// Initialize cache
|
|
133
|
+
cache = new SQLiteCache(config);
|
|
134
|
+
await cache.load();
|
|
135
|
+
|
|
136
|
+
// Initialize features
|
|
137
|
+
indexer = new CodebaseIndexer(embedder, cache, config, server);
|
|
138
|
+
hybridSearch = new HybridSearch(embedder, cache, config, indexer);
|
|
139
|
+
const cacheClearer = new ClearCacheFeature.CacheClearer(
|
|
140
|
+
embedder,
|
|
141
|
+
cache,
|
|
142
|
+
config,
|
|
143
|
+
indexer
|
|
144
|
+
);
|
|
145
|
+
const versionChecker = new CheckLastVersionFeature.VersionChecker(config);
|
|
146
|
+
|
|
147
|
+
// Store feature instances (matches features array order)
|
|
148
|
+
features[0].instance = hybridSearch;
|
|
149
|
+
features[1].instance = indexer;
|
|
150
|
+
features[2].instance = cacheClearer;
|
|
151
|
+
features[3].instance = versionChecker;
|
|
152
|
+
|
|
153
|
+
// Initialize new tools
|
|
154
|
+
const workspaceManager = new SetWorkspaceFeature.WorkspaceManager(
|
|
155
|
+
config,
|
|
156
|
+
cache,
|
|
157
|
+
indexer
|
|
158
|
+
);
|
|
159
|
+
const statusReporter = new GetStatusFeature.StatusReporter(
|
|
160
|
+
config,
|
|
161
|
+
cache,
|
|
162
|
+
indexer,
|
|
163
|
+
embedder
|
|
164
|
+
);
|
|
165
|
+
features[4].instance = workspaceManager;
|
|
166
|
+
features[5].instance = statusReporter;
|
|
167
|
+
|
|
168
|
+
isInitialized = true;
|
|
169
|
+
console.error("[Server] Model and cache loaded successfully");
|
|
170
|
+
})();
|
|
171
|
+
|
|
172
|
+
await initializationPromise;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Initialize application (lightweight, non-blocking)
|
|
97
176
|
async function initialize() {
|
|
98
177
|
// Load configuration with workspace support
|
|
99
178
|
config = await loadConfig(workspaceDir);
|
|
100
|
-
|
|
179
|
+
|
|
101
180
|
// Ensure search directory exists
|
|
102
181
|
try {
|
|
103
182
|
await fs.access(config.searchDirectory);
|
|
104
183
|
} catch {
|
|
105
|
-
console.error(
|
|
184
|
+
console.error(
|
|
185
|
+
`[Server] Error: Search directory "${config.searchDirectory}" does not exist`
|
|
186
|
+
);
|
|
106
187
|
process.exit(1);
|
|
107
188
|
}
|
|
108
189
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const workspaceManager = new SetWorkspaceFeature.WorkspaceManager(config, cache, indexer);
|
|
132
|
-
const statusReporter = new GetStatusFeature.StatusReporter(config, cache, indexer, embedder);
|
|
133
|
-
features[4].instance = workspaceManager;
|
|
134
|
-
features[5].instance = statusReporter;
|
|
135
|
-
|
|
136
|
-
// Start indexing in background (non-blocking)
|
|
137
|
-
console.error("[Server] Starting background indexing...");
|
|
138
|
-
indexer.indexAll().then(() => {
|
|
139
|
-
// Only start file watcher if explicitly enabled in config
|
|
140
|
-
if (config.watchFiles) {
|
|
141
|
-
indexer.setupFileWatcher();
|
|
142
|
-
}
|
|
143
|
-
}).catch(err => {
|
|
144
|
-
console.error("[Server] Background indexing error:", err.message);
|
|
145
|
-
});
|
|
190
|
+
console.error(
|
|
191
|
+
"[Server] Configuration loaded. Model will load on first use (lazy initialization)."
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
// Optional: auto-index after delay
|
|
195
|
+
if (config.autoIndexDelay && config.autoIndexDelay > 0) {
|
|
196
|
+
console.error(
|
|
197
|
+
`[Server] Auto-indexing will start after ${config.autoIndexDelay}ms delay...`
|
|
198
|
+
);
|
|
199
|
+
setTimeout(async () => {
|
|
200
|
+
console.error("[Server] Starting auto-indexing...");
|
|
201
|
+
try {
|
|
202
|
+
await ensureInitialized();
|
|
203
|
+
await indexer.indexAll();
|
|
204
|
+
if (config.watchFiles) {
|
|
205
|
+
indexer.setupFileWatcher();
|
|
206
|
+
}
|
|
207
|
+
} catch (err) {
|
|
208
|
+
console.error("[Server] Auto-indexing error:", err.message);
|
|
209
|
+
}
|
|
210
|
+
}, config.autoIndexDelay);
|
|
211
|
+
}
|
|
146
212
|
}
|
|
147
213
|
|
|
148
214
|
// Setup MCP server
|
|
149
215
|
const server = new Server(
|
|
150
|
-
{
|
|
151
|
-
name: "smart-coding-mcp",
|
|
152
|
-
version: packageJson.version
|
|
216
|
+
{
|
|
217
|
+
name: "smart-coding-mcp",
|
|
218
|
+
version: packageJson.version,
|
|
153
219
|
},
|
|
154
|
-
{
|
|
155
|
-
capabilities: {
|
|
156
|
-
tools: {}
|
|
157
|
-
}
|
|
220
|
+
{
|
|
221
|
+
capabilities: {
|
|
222
|
+
tools: {},
|
|
223
|
+
},
|
|
158
224
|
}
|
|
159
225
|
);
|
|
160
226
|
|
|
161
227
|
// Register tools from all features
|
|
162
228
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
163
229
|
const tools = [];
|
|
164
|
-
|
|
230
|
+
|
|
165
231
|
for (const feature of features) {
|
|
166
232
|
const toolDef = feature.module.getToolDefinition(config);
|
|
167
233
|
tools.push(toolDef);
|
|
@@ -172,6 +238,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
172
238
|
|
|
173
239
|
// Handle tool calls
|
|
174
240
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
241
|
+
// Ensure model and cache are loaded before handling any tool
|
|
242
|
+
await ensureInitialized();
|
|
243
|
+
|
|
175
244
|
for (const feature of features) {
|
|
176
245
|
const toolDef = feature.module.getToolDefinition(config);
|
|
177
246
|
|
|
@@ -191,36 +260,36 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
191
260
|
// Main entry point
|
|
192
261
|
async function main() {
|
|
193
262
|
await initialize();
|
|
194
|
-
|
|
263
|
+
|
|
195
264
|
const transport = new StdioServerTransport();
|
|
196
265
|
await server.connect(transport);
|
|
197
|
-
|
|
266
|
+
|
|
198
267
|
console.error("[Server] Smart Coding MCP server ready!");
|
|
199
268
|
}
|
|
200
269
|
|
|
201
270
|
// Graceful shutdown
|
|
202
|
-
process.on(
|
|
271
|
+
process.on("SIGINT", async () => {
|
|
203
272
|
console.error("\n[Server] Shutting down gracefully...");
|
|
204
|
-
|
|
273
|
+
|
|
205
274
|
// Stop file watcher
|
|
206
275
|
if (indexer && indexer.watcher) {
|
|
207
276
|
await indexer.watcher.close();
|
|
208
277
|
console.error("[Server] File watcher stopped");
|
|
209
278
|
}
|
|
210
|
-
|
|
279
|
+
|
|
211
280
|
// Save cache
|
|
212
281
|
if (cache) {
|
|
213
282
|
await cache.save();
|
|
214
283
|
console.error("[Server] Cache saved");
|
|
215
284
|
}
|
|
216
|
-
|
|
285
|
+
|
|
217
286
|
console.error("[Server] Goodbye!");
|
|
218
287
|
process.exit(0);
|
|
219
288
|
});
|
|
220
289
|
|
|
221
|
-
process.on(
|
|
290
|
+
process.on("SIGTERM", async () => {
|
|
222
291
|
console.error("\n[Server] Received SIGTERM, shutting down...");
|
|
223
292
|
process.exit(0);
|
|
224
293
|
});
|
|
225
294
|
|
|
226
|
-
main().catch(console.error);
|
|
295
|
+
main().catch(console.error);
|
package/lib/config.js
CHANGED
|
@@ -63,12 +63,24 @@ const DEFAULT_CONFIG = {
|
|
|
63
63
|
verbose: false,
|
|
64
64
|
workerThreads: "auto", // "auto" = CPU cores - 1, or set a number
|
|
65
65
|
embeddingModel: "nomic-ai/nomic-embed-text-v1.5",
|
|
66
|
-
embeddingDimension:
|
|
66
|
+
embeddingDimension: 128, // MRL dimension: 64, 128, 256, 512, 768 (changed from 256 to 128 for better performance)
|
|
67
67
|
device: "auto", // "cpu", "webgpu", or "auto"
|
|
68
68
|
chunkingMode: "smart", // "smart", "ast", or "line"
|
|
69
69
|
semanticWeight: 0.7,
|
|
70
70
|
exactMatchBoost: 1.5,
|
|
71
|
-
smartIndexing: true
|
|
71
|
+
smartIndexing: true,
|
|
72
|
+
|
|
73
|
+
// Resource throttling (prevents CPU exhaustion)
|
|
74
|
+
maxCpuPercent: 25, // Max CPU usage during indexing (default: 25%)
|
|
75
|
+
batchDelay: 250, // Delay between batches in ms (default: 250)
|
|
76
|
+
maxWorkers: 'auto', // Max worker threads ('auto' = 25% of cores, or specific number)
|
|
77
|
+
|
|
78
|
+
// Startup behavior
|
|
79
|
+
autoIndexDelay: null, // Delay before auto-indexing (ms), null = disabled (lazy index on first search)
|
|
80
|
+
|
|
81
|
+
// Progressive indexing
|
|
82
|
+
incrementalSaveInterval: 5, // Save to cache every N batches
|
|
83
|
+
allowPartialSearch: true // Allow searches while indexing is in progress
|
|
72
84
|
};
|
|
73
85
|
|
|
74
86
|
let config = { ...DEFAULT_CONFIG };
|
|
@@ -276,6 +288,44 @@ export async function loadConfig(workspaceDir = null) {
|
|
|
276
288
|
}
|
|
277
289
|
}
|
|
278
290
|
|
|
291
|
+
// Resource throttling - Max CPU percent
|
|
292
|
+
if (process.env.SMART_CODING_MAX_CPU_PERCENT !== undefined) {
|
|
293
|
+
const value = parseInt(process.env.SMART_CODING_MAX_CPU_PERCENT, 10);
|
|
294
|
+
if (!isNaN(value) && value >= 10 && value <= 100) {
|
|
295
|
+
config.maxCpuPercent = value;
|
|
296
|
+
console.error(`[Config] Max CPU usage: ${value}%`);
|
|
297
|
+
} else {
|
|
298
|
+
console.error(`[Config] Invalid SMART_CODING_MAX_CPU_PERCENT: ${value}, using default (must be 10-100)`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Resource throttling - Batch delay
|
|
303
|
+
if (process.env.SMART_CODING_BATCH_DELAY !== undefined) {
|
|
304
|
+
const value = parseInt(process.env.SMART_CODING_BATCH_DELAY, 10);
|
|
305
|
+
if (!isNaN(value) && value >= 0 && value <= 5000) {
|
|
306
|
+
config.batchDelay = value;
|
|
307
|
+
console.error(`[Config] Batch delay: ${value}ms`);
|
|
308
|
+
} else {
|
|
309
|
+
console.error(`[Config] Invalid SMART_CODING_BATCH_DELAY: ${value}, using default (must be 0-5000)`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Resource throttling - Max workers
|
|
314
|
+
if (process.env.SMART_CODING_MAX_WORKERS !== undefined) {
|
|
315
|
+
const value = process.env.SMART_CODING_MAX_WORKERS.trim().toLowerCase();
|
|
316
|
+
if (value === 'auto') {
|
|
317
|
+
config.maxWorkers = 'auto';
|
|
318
|
+
} else {
|
|
319
|
+
const numValue = parseInt(value, 10);
|
|
320
|
+
if (!isNaN(numValue) && numValue >= 1 && numValue <= 32) {
|
|
321
|
+
config.maxWorkers = numValue;
|
|
322
|
+
console.error(`[Config] Max workers: ${numValue}`);
|
|
323
|
+
} else {
|
|
324
|
+
console.error(`[Config] Invalid SMART_CODING_MAX_WORKERS: ${value}, using default (must be 'auto' or 1-32)`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
279
329
|
return config;
|
|
280
330
|
}
|
|
281
331
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resource throttling utility to prevent CPU/memory exhaustion during indexing
|
|
5
|
+
* Ensures the MCP server doesn't freeze the user's laptop
|
|
6
|
+
*/
|
|
7
|
+
export class ResourceThrottle {
|
|
8
|
+
constructor(config) {
|
|
9
|
+
// Max CPU usage as percentage (default 50%)
|
|
10
|
+
this.maxCpuPercent = config.maxCpuPercent || 50;
|
|
11
|
+
|
|
12
|
+
// Delay between batches in milliseconds
|
|
13
|
+
this.batchDelay = config.batchDelay || 100;
|
|
14
|
+
|
|
15
|
+
// Max worker threads (override auto-detection)
|
|
16
|
+
const cpuCount = os.cpus().length;
|
|
17
|
+
if (config.maxWorkers === 'auto' || config.maxWorkers === undefined) {
|
|
18
|
+
// Use 25% of cores by default for throttling (more conservative)
|
|
19
|
+
this.maxWorkers = Math.max(1, Math.floor(cpuCount * 0.25));
|
|
20
|
+
} else {
|
|
21
|
+
// Validate and parse the value
|
|
22
|
+
const parsed = typeof config.maxWorkers === 'number'
|
|
23
|
+
? config.maxWorkers
|
|
24
|
+
: parseInt(config.maxWorkers, 10);
|
|
25
|
+
|
|
26
|
+
if (isNaN(parsed) || parsed < 1) {
|
|
27
|
+
console.error(`[Throttle] Invalid maxWorkers: ${config.maxWorkers}, using auto`);
|
|
28
|
+
this.maxWorkers = Math.max(1, Math.floor(cpuCount * 0.5));
|
|
29
|
+
} else {
|
|
30
|
+
this.maxWorkers = Math.max(1, Math.min(parsed, cpuCount));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.error(`[Throttle] CPU limit: ${this.maxCpuPercent}%, Batch delay: ${this.batchDelay}ms, Max workers: ${this.maxWorkers}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Execute work with delay to throttle CPU usage
|
|
39
|
+
*/
|
|
40
|
+
async throttledBatch(work, signal = null) {
|
|
41
|
+
// Execute the work
|
|
42
|
+
if (work) {
|
|
43
|
+
await work();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Apply delay if not aborted
|
|
47
|
+
if (!signal?.aborted && this.batchDelay > 0) {
|
|
48
|
+
await this.sleep(this.batchDelay);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Sleep utility
|
|
54
|
+
*/
|
|
55
|
+
sleep(ms) {
|
|
56
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Calculate optimal worker count based on CPU limit
|
|
61
|
+
*/
|
|
62
|
+
getWorkerCount(requestedWorkers) {
|
|
63
|
+
if (requestedWorkers === 'auto') {
|
|
64
|
+
return this.maxWorkers;
|
|
65
|
+
}
|
|
66
|
+
return Math.min(requestedWorkers, this.maxWorkers);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check if we should pause due to high CPU usage
|
|
71
|
+
* This is a simple implementation - could be enhanced with actual CPU monitoring
|
|
72
|
+
*/
|
|
73
|
+
async checkCpuUsage() {
|
|
74
|
+
// Future enhancement: monitor actual CPU usage and pause if needed
|
|
75
|
+
// For now, we rely on worker limits and batch delays
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Sleep utility function
|
|
82
|
+
*/
|
|
83
|
+
export function sleep(ms) {
|
|
84
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
85
|
+
}
|