@softerist/heuristic-mcp 3.0.17 → 3.1.0

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.
@@ -1,8 +1,8 @@
1
- // Comprehensive ignore patterns based on industry best practices
2
- // Researched from gitignore templates and development community standards
1
+
2
+
3
3
 
4
4
  export const IGNORE_PATTERNS = {
5
- // JavaScript/Node.js
5
+
6
6
  javascript: [
7
7
  '**/node_modules/**',
8
8
  '**/.next/**',
@@ -26,7 +26,7 @@ export const IGNORE_PATTERNS = {
26
26
  '**/yarn.lock',
27
27
  ],
28
28
 
29
- // Python
29
+
30
30
  python: [
31
31
  '**/__pycache__/**',
32
32
  '**/*.pyc',
@@ -59,7 +59,7 @@ export const IGNORE_PATTERNS = {
59
59
  '**/.ruff_cache/**',
60
60
  ],
61
61
 
62
- // Java/Maven
62
+
63
63
  java: [
64
64
  '**/target/**',
65
65
  '**/.gradle/**',
@@ -79,7 +79,7 @@ export const IGNORE_PATTERNS = {
79
79
  '**/*.ear',
80
80
  ],
81
81
 
82
- // Android
82
+
83
83
  android: [
84
84
  '**/.gradle/**',
85
85
  '**/build/**',
@@ -98,7 +98,7 @@ export const IGNORE_PATTERNS = {
98
98
  '**/.navigation/**',
99
99
  ],
100
100
 
101
- // iOS/Swift
101
+
102
102
  ios: [
103
103
  '**/Pods/**',
104
104
  '**/DerivedData/**',
@@ -119,19 +119,19 @@ export const IGNORE_PATTERNS = {
119
119
  '**/*.ipa',
120
120
  ],
121
121
 
122
- // Go
122
+
123
123
  go: ['**/vendor/**', '**/bin/**', '**/pkg/**', '**/*.exe', '**/*.test', '**/*.prof'],
124
124
 
125
- // PHP
125
+
126
126
  php: ['**/vendor/**', '**/composer.phar', '**/composer.lock', '**/.phpunit.result.cache'],
127
127
 
128
- // Rust
128
+
129
129
  rust: ['**/target/**', '**/Cargo.lock', '**/*.rs.bk'],
130
130
 
131
- // Ruby
131
+
132
132
  ruby: ['**/vendor/bundle/**', '**/.bundle/**', '**/Gemfile.lock', '**/.byebug_history'],
133
133
 
134
- // .NET/C#
134
+
135
135
  dotnet: [
136
136
  '**/bin/**',
137
137
  '**/obj/**',
@@ -142,21 +142,21 @@ export const IGNORE_PATTERNS = {
142
142
  '**/node_modules/**',
143
143
  ],
144
144
 
145
- // Common (IDE, OS, Build tools)
145
+
146
146
  common: [
147
- // Version control
147
+
148
148
  '**/.git/**',
149
149
  '**/.svn/**',
150
150
  '**/.hg/**',
151
151
  '**/.bzr/**',
152
152
 
153
- // OS files
153
+
154
154
  '**/.DS_Store',
155
155
  '**/Thumbs.db',
156
156
  '**/desktop.ini',
157
157
  '**/$RECYCLE.BIN/**',
158
158
 
159
- // Backup files
159
+
160
160
  '**/*.bak',
161
161
  '**/*.backup',
162
162
  '**/*~',
@@ -166,16 +166,16 @@ export const IGNORE_PATTERNS = {
166
166
  '**/#*#',
167
167
  '**/.#*',
168
168
 
169
- // Lock files (editor/runtime, not package managers)
169
+
170
170
  '**/*.lock',
171
171
  '**/.~lock*',
172
172
 
173
- // Logs
173
+
174
174
  '**/*.log',
175
175
  '**/logs/**',
176
176
  '**/*.log.*',
177
177
 
178
- // IDEs and Editors
178
+
179
179
  '**/.vscode/**',
180
180
  '**/.idea/**',
181
181
  '**/.sublime-project',
@@ -191,19 +191,19 @@ export const IGNORE_PATTERNS = {
191
191
  '**/*.tmproject',
192
192
  '**/tmtags',
193
193
 
194
- // Vim
194
+
195
195
  '**/*~',
196
196
  '**/*.swp',
197
197
  '**/*.swo',
198
198
  '**/.*.sw?',
199
199
  '**/Session.vim',
200
200
 
201
- // Emacs
201
+
202
202
  '**/*~',
203
203
  '**/#*#',
204
204
  '**/.#*',
205
205
 
206
- // Environment files (secrets)
206
+
207
207
  '**/.env',
208
208
  '**/.env.local',
209
209
  '**/.env.*.local',
@@ -220,21 +220,21 @@ export const IGNORE_PATTERNS = {
220
220
  '**/*.p12',
221
221
  '**/*.pfx',
222
222
 
223
- // Temporary files
223
+
224
224
  '**/tmp/**',
225
225
  '**/temp/**',
226
226
  '**/*.tmp',
227
227
  '**/*.temp',
228
228
  '**/.cache/**',
229
229
 
230
- // Session & runtime
230
+
231
231
  '**/.sass-cache/**',
232
232
  '**/connect.lock',
233
233
  '**/*.pid',
234
234
  '**/*.seed',
235
235
  '**/*.pid.lock',
236
236
 
237
- // Coverage & test output
237
+
238
238
  '**/coverage/**',
239
239
  '**/.nyc_output/**',
240
240
  '**/test-results/**',
@@ -242,61 +242,61 @@ export const IGNORE_PATTERNS = {
242
242
  '**/*.coverage',
243
243
  '**/htmlcov/**',
244
244
 
245
- // Documentation builds
245
+
246
246
  '**/docs/_build/**',
247
247
  '**/site/**',
248
248
 
249
- // Misc
249
+
250
250
  '**/*.orig',
251
251
  '**/core',
252
252
  '**/*.core',
253
253
  ],
254
254
  };
255
255
 
256
- // Map marker files to project types
256
+
257
257
  export const FILE_TYPE_MAP = {
258
- // JavaScript/Node
258
+
259
259
  'package.json': 'javascript',
260
260
  'package-lock.json': 'javascript',
261
261
  'yarn.lock': 'javascript',
262
262
  'pnpm-lock.yaml': 'javascript',
263
263
 
264
- // Python
264
+
265
265
  'requirements.txt': 'python',
266
266
  Pipfile: 'python',
267
267
  'pyproject.toml': 'python',
268
268
  'setup.py': 'python',
269
269
 
270
- // Android
270
+
271
271
  'build.gradle': 'android',
272
272
  'build.gradle.kts': 'android',
273
273
  'settings.gradle': 'android',
274
274
 
275
- // Java
275
+
276
276
  'pom.xml': 'java',
277
277
 
278
- // iOS
278
+
279
279
  Podfile: 'ios',
280
280
  'Package.swift': 'ios',
281
281
 
282
- // Go
282
+
283
283
  'go.mod': 'go',
284
284
 
285
- // PHP
285
+
286
286
  'composer.json': 'php',
287
287
 
288
- // Rust
288
+
289
289
  'Cargo.toml': 'rust',
290
290
 
291
- // Ruby
291
+
292
292
  Gemfile: 'ruby',
293
293
 
294
- // .NET
294
+
295
295
  '*.csproj': 'dotnet',
296
296
  '*.sln': 'dotnet',
297
297
  };
298
298
 
299
- // Directories to skip during project detection (recursion)
299
+
300
300
  export const SKIP_DIRECTORIES = [
301
301
  'node_modules',
302
302
  'dist',
@@ -20,29 +20,9 @@ function onceDrainOrError(stream) {
20
20
  });
21
21
  }
22
22
 
23
- /**
24
- * Streaming JSON array writer optimized for:
25
- * - TypedArray vectors streamed (no per-item vector allocation)
26
- * - backpressure safety
27
- * - configurable float rounding + flush threshold
28
- * - compact mode when indent === '' (no forced newlines)
29
- * - safe cleanup on failure (abort)
30
- * - optional native TypedArray.join(',') fast-path when rounding is disabled
31
- */
23
+
32
24
  export class StreamingJsonWriter {
33
- /**
34
- * @param {string} filePath
35
- * @param {object} [opts]
36
- * @param {number} [opts.highWaterMark] Stream internal buffer size.
37
- * @param {number|null} [opts.floatDigits] Round floats to N digits. null disables rounding.
38
- * @param {number} [opts.flushChars] Flush threshold for the internal string buffer.
39
- * @param {string} [opts.indent] Indent prefix per item ("" for compact, " " for pretty).
40
- * @param {boolean} [opts.assumeFinite] Skip NaN/Infinity checks (unsafe if false data).
41
- * @param {boolean} [opts.checkFinite] If set, overrides assumeFinite (true = check, false = skip).
42
- * @param {boolean} [opts.noMutation] Avoid temporary mutation when stripping vector.
43
- * @param {number} [opts.joinThreshold] Max elements to use single join() string.
44
- * @param {number} [opts.joinChunkSize] Elements per join() chunk when chunking.
45
- */
25
+
46
26
  constructor(
47
27
  filePath,
48
28
  {
@@ -78,7 +58,7 @@ export class StreamingJsonWriter {
78
58
  this.first = true;
79
59
  this._streamError = null;
80
60
 
81
- // Formatter + fast-path flag
61
+
82
62
  this._useJoinFastPath = floatDigits === null;
83
63
 
84
64
  if (!this._useJoinFastPath) {
@@ -129,17 +109,14 @@ export class StreamingJsonWriter {
129
109
  this.first = true;
130
110
  }
131
111
 
132
- /**
133
- * Best-effort early shutdown (use in catch/finally blocks).
134
- * Destroys the stream to avoid fd leaks when writeEnd() is not reached.
135
- */
112
+
136
113
  abort(err) {
137
114
  if (!this.stream) return;
138
115
  try {
139
116
  this._streamError = err || this._streamError || new Error('StreamingJsonWriter aborted');
140
117
  this.stream.destroy(this._streamError);
141
118
  } catch {
142
- // ignore
119
+
143
120
  } finally {
144
121
  this.stream = null;
145
122
  }
@@ -214,7 +191,7 @@ export class StreamingJsonWriter {
214
191
  try {
215
192
  item.vector = vec;
216
193
  } catch {
217
- // ignore
194
+
218
195
  }
219
196
  }
220
197
  }
@@ -234,11 +211,7 @@ export class StreamingJsonWriter {
234
211
  }
235
212
  }
236
213
 
237
- /**
238
- * Core write method.
239
- * Returns null on synchronous success (fast path).
240
- * Returns a Promise only when backpressure is hit (slow path).
241
- */
214
+
242
215
  _writeRaw(str) {
243
216
  if (this._streamError) throw this._streamError;
244
217
 
package/lib/logging.js CHANGED
@@ -12,7 +12,7 @@ const originalConsole = {
12
12
  };
13
13
 
14
14
  export function enableStderrOnlyLogging() {
15
- // Keep MCP stdout clean by routing all console output to stderr.
15
+
16
16
  const redirect = (...args) => originalConsole.error(...args);
17
17
  // eslint-disable-next-line no-console
18
18
  console.log = redirect;
@@ -36,7 +36,7 @@ export async function setupFileLogging(config) {
36
36
  const writeLine = (level, args) => {
37
37
  if (!logStream) return;
38
38
  const message = util.format(...args);
39
- // Skip empty lines (spacers) in log files
39
+
40
40
  if (!message.trim()) return;
41
41
 
42
42
  const timestamp = new Date().toISOString();
@@ -53,7 +53,7 @@ export async function setupFileLogging(config) {
53
53
  const originalError = originalConsole.error;
54
54
  // eslint-disable-next-line no-console
55
55
  console[method] = (...args) => {
56
- // Always send to original stderr to avoid MCP protocol pollution on stdout
56
+
57
57
  originalError(...args);
58
58
  writeLine(level, args);
59
59
  };
@@ -89,25 +89,11 @@ export async function ensureLogDirectory(config) {
89
89
  return logPath;
90
90
  }
91
91
 
92
- // ============================================================================
93
- // Error Handling Utilities
94
- // ============================================================================
95
-
96
- /*
97
- * Error handling patterns used in this codebase:
98
- *
99
- * SILENT_EXPECTED: Empty catch for expected failures (file not found, cleanup)
100
- * Use for: fs.stat on optional files, cleanup on exit, optional features
101
- *
102
- * LOG_AND_CONTINUE: Warn but continue execution
103
- * Use for: Non-critical features, fallback scenarios
104
- *
105
- * LOG_AND_RETHROW: Log context then propagate to caller
106
- * Use for: Fatal errors that caller must handle
107
- *
108
- * VERBOSE_ONLY: Only log when verbose mode is enabled
109
- * Use for: Performance diagnostics, debug information
110
- */
92
+
93
+
94
+
95
+
96
+
111
97
  export const ERROR_PATTERNS = {
112
98
  SILENT_EXPECTED: 'silent_expected',
113
99
  LOG_AND_CONTINUE: 'log_and_continue',
@@ -115,11 +101,7 @@ export const ERROR_PATTERNS = {
115
101
  VERBOSE_ONLY: 'verbose_only',
116
102
  };
117
103
 
118
- /**
119
- * Log message only when verbose mode is enabled.
120
- * @param {object|boolean} configOrVerbose - Config object with verbose property, or boolean
121
- * @param {...any} args - Arguments to log
122
- */
104
+
123
105
  export function logVerbose(configOrVerbose, ...args) {
124
106
  const isVerbose = typeof configOrVerbose === 'boolean'
125
107
  ? configOrVerbose
@@ -129,15 +111,7 @@ export function logVerbose(configOrVerbose, ...args) {
129
111
  }
130
112
  }
131
113
 
132
- /**
133
- * Log a recoverable error with consistent formatting.
134
- * Use when the error is non-fatal and execution can continue.
135
- * @param {string} context - Where the error occurred
136
- * @param {Error} error - The caught error
137
- * @param {object} options - Optional configuration
138
- * @param {boolean} options.verbose - If true, log full stack trace
139
- * @param {string} options.fallbackAction - Description of fallback behavior
140
- */
114
+
141
115
  export function logRecoverableError(context, error, options = {}) {
142
116
  const message = error?.message || String(error);
143
117
  const prefix = `[${context}]`;
@@ -153,12 +127,7 @@ export function logRecoverableError(context, error, options = {}) {
153
127
  }
154
128
  }
155
129
 
156
- /**
157
- * Check if an error is expected and should be silently ignored.
158
- * @param {Error} error - The caught error
159
- * @param {string[]} expectedCodes - Expected error codes
160
- * @returns {boolean} True if error matches an expected code
161
- */
130
+
162
131
  export function isExpectedError(error, expectedCodes = ['ENOENT', 'ENOTDIR']) {
163
132
  return error && typeof error.code === 'string' && expectedCodes.includes(error.code);
164
133
  }
@@ -4,7 +4,7 @@ import { createRequire } from 'module';
4
4
  const require = createRequire(import.meta.url);
5
5
  const IS_TEST_ENV = process.env.VITEST === 'true' || process.env.NODE_ENV === 'test';
6
6
 
7
- // Try to load onnxruntime-node, fallback to null if not available (e.g., Node v25+)
7
+
8
8
  let ort = null;
9
9
  let ortLoadError = null;
10
10
  try {
@@ -30,18 +30,18 @@ try {
30
30
  expectedOrtVersion = null;
31
31
  }
32
32
 
33
- // Try to import executionProviders for configuration
33
+
34
34
  let executionProviders = null;
35
35
  let ONNX = null;
36
36
  if (!IS_TEST_ENV) {
37
37
  try {
38
- // Avoid Vite import-analysis on deep import by using an indirect dynamic import.
38
+
39
39
  const dynamicImport = new Function('specifier', 'return import(specifier)');
40
40
  const backend = await dynamicImport('@huggingface/transformers/src/backends/onnx.js');
41
41
  executionProviders = backend.executionProviders;
42
42
  ONNX = backend.ONNX;
43
43
  } catch {
44
- // Not available in all transformers.js versions
44
+
45
45
  }
46
46
  }
47
47
 
package/lib/path-utils.js CHANGED
@@ -1,28 +1,15 @@
1
- /**
2
- * Centralized path normalization utilities for cross-platform consistency.
3
- * Addresses Issue #13 from code review: inconsistent path normalization.
4
- */
1
+
5
2
 
6
3
  import path from 'path';
7
4
 
8
- /**
9
- * Normalize a file path for consistent comparison.
10
- * Converts backslashes to forward slashes and lowercases on Windows.
11
- * @param {string} value - Path to normalize
12
- * @returns {string} Normalized path suitable for comparison
13
- */
5
+
14
6
  export function normalizePath(value) {
15
7
  if (typeof value !== 'string') return '';
16
8
  const normalized = value.replace(/\\/g, '/');
17
9
  return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
18
10
  }
19
11
 
20
- /**
21
- * Check if a target path is inside a base directory.
22
- * @param {string} basePath - Base directory path
23
- * @param {string} targetPath - Path to check
24
- * @returns {boolean} True if targetPath is inside basePath
25
- */
12
+
26
13
  export function isPathInside(basePath, targetPath) {
27
14
  const normalizedBase = normalizePath(basePath);
28
15
  const normalizedTarget = normalizePath(targetPath);
@@ -30,11 +17,7 @@ export function isPathInside(basePath, targetPath) {
30
17
  return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
31
18
  }
32
19
 
33
- /**
34
- * Normalize a path for use as a cache key or map lookup.
35
- * @param {string} filePath - Path to normalize
36
- * @returns {string} Normalized path key
37
- */
20
+
38
21
  export function normalizePathKey(filePath) {
39
22
  return normalizePath(filePath);
40
23
  }
@@ -12,7 +12,7 @@ export class ProjectDetector {
12
12
  const maxDepth = typeof options.maxDepth === 'number' ? options.maxDepth : 2;
13
13
  const startDepth = typeof options.startDepth === 'number' ? options.startDepth : 0;
14
14
  const markerFiles = Object.keys(FILE_TYPE_MAP);
15
- const discoveredTypes = new Map(); // type -> first marker found
15
+ const discoveredTypes = new Map();
16
16
 
17
17
  const checkDir = async (dir, depth) => {
18
18
  if (depth > maxDepth) return;
@@ -38,7 +38,7 @@ export class ProjectDetector {
38
38
  }
39
39
  }
40
40
 
41
- // Recurse into subdirectories
41
+
42
42
  if (depth < maxDepth) {
43
43
  for (const item of items) {
44
44
  if (item.isDirectory()) {
@@ -71,7 +71,7 @@ export class ProjectDetector {
71
71
  }
72
72
  }
73
73
 
74
- // Remove duplicates
74
+
75
75
  return [...new Set(patterns)];
76
76
  }
77
77
 
@@ -1,7 +1,8 @@
1
- import fs from 'fs/promises';
2
- import fsSync from 'fs';
3
- import path from 'path';
4
- import os from 'os';
1
+ import fs from 'fs/promises';
2
+ import fsSync from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { setTimeout as delay } from 'timers/promises';
5
6
 
6
7
  function isTestEnv() {
7
8
  return process.env.VITEST === 'true' || process.env.NODE_ENV === 'test';
@@ -27,10 +28,10 @@ export async function setupPidFile({
27
28
  try {
28
29
  await fs.mkdir(cacheDirectory, { recursive: true });
29
30
  } catch {
30
- // ignore mkdir errors and attempt to write anyway
31
+
31
32
  }
32
33
  }
33
- // Clean up stale PID file if present
34
+
34
35
  try {
35
36
  const raw = await fs.readFile(pidPath, 'utf-8');
36
37
  const existingPid = parseInt(String(raw).trim(), 10);
@@ -38,7 +39,7 @@ export async function setupPidFile({
38
39
  await fs.unlink(pidPath).catch(() => {});
39
40
  }
40
41
  } catch {
41
- // ignore missing/invalid pid file
42
+
42
43
  }
43
44
  try {
44
45
  await fs.writeFile(pidPath, `${process.pid}`, 'utf-8');
@@ -51,7 +52,7 @@ export async function setupPidFile({
51
52
  try {
52
53
  fsSync.unlinkSync(pidPath);
53
54
  } catch {
54
- // ignore
55
+
55
56
  }
56
57
  };
57
58
 
@@ -69,7 +70,7 @@ function isProcessRunning(pid) {
69
70
  process.kill(pid, 0);
70
71
  return true;
71
72
  } catch (err) {
72
- // On Windows, EPERM can happen even if process exists.
73
+
73
74
  if (err && err.code === 'EPERM') {
74
75
  return true;
75
76
  }
@@ -137,7 +138,7 @@ export async function acquireWorkspaceLock({ cacheDirectory, workspaceDir = null
137
138
  fsSync.unlinkSync(lockPath);
138
139
  }
139
140
  } catch {
140
- // ignore cleanup errors
141
+
141
142
  }
142
143
  };
143
144
 
@@ -148,7 +149,7 @@ export async function acquireWorkspaceLock({ cacheDirectory, workspaceDir = null
148
149
  return { acquired: true, lockPath };
149
150
  }
150
151
 
151
- export async function releaseWorkspaceLock({ cacheDirectory } = {}) {
152
+ export async function releaseWorkspaceLock({ cacheDirectory } = {}) {
152
153
  if (!cacheDirectory || isTestEnv()) {
153
154
  return;
154
155
  }
@@ -159,7 +160,100 @@ export async function releaseWorkspaceLock({ cacheDirectory } = {}) {
159
160
  if (current && current.pid === process.pid) {
160
161
  await fs.unlink(lockPath).catch(() => {});
161
162
  }
162
- } catch {
163
- // ignore release errors
164
- }
165
- }
163
+ } catch {
164
+
165
+ }
166
+ }
167
+
168
+ async function terminateProcess(pid, { graceMs = 1500 } = {}) {
169
+ if (!Number.isInteger(pid) || pid <= 0) return false;
170
+ if (!isProcessRunning(pid)) return true;
171
+
172
+ try {
173
+ process.kill(pid, 'SIGTERM');
174
+ } catch {
175
+
176
+ }
177
+
178
+ const pollCount = Math.max(1, Math.ceil(graceMs / 100));
179
+ for (let i = 0; i < pollCount; i++) {
180
+ if (!isProcessRunning(pid)) return true;
181
+ await delay(100);
182
+ }
183
+
184
+ try {
185
+ process.kill(pid, 'SIGKILL');
186
+ } catch {
187
+
188
+ }
189
+
190
+ await delay(100);
191
+ return !isProcessRunning(pid);
192
+ }
193
+
194
+ export async function stopOtherHeuristicServers({
195
+ globalCacheRoot = null,
196
+ currentCacheDirectory = null,
197
+ } = {}) {
198
+ if (isTestEnv() || !globalCacheRoot) {
199
+ return { killed: [], failed: [] };
200
+ }
201
+
202
+ const normalizeForCompare = (value) => {
203
+ if (!value) return '';
204
+ const resolved = path.resolve(value);
205
+ return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
206
+ };
207
+ const currentCacheDirResolved = normalizeForCompare(currentCacheDirectory);
208
+
209
+ let dirEntries = [];
210
+ try {
211
+ dirEntries = await fs.readdir(globalCacheRoot, { withFileTypes: true });
212
+ } catch {
213
+ return { killed: [], failed: [] };
214
+ }
215
+
216
+ const lockOwners = [];
217
+ for (const entry of dirEntries) {
218
+ if (!entry.isDirectory()) continue;
219
+ const cacheDirectory = path.join(globalCacheRoot, entry.name);
220
+ if (currentCacheDirResolved && normalizeForCompare(cacheDirectory) === currentCacheDirResolved) {
221
+ continue;
222
+ }
223
+
224
+ const lockPath = path.join(cacheDirectory, 'server.lock.json');
225
+ let lock = null;
226
+ try {
227
+ lock = JSON.parse(await fs.readFile(lockPath, 'utf-8'));
228
+ } catch {
229
+ continue;
230
+ }
231
+
232
+ const pid = Number(lock?.pid);
233
+ if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid) continue;
234
+
235
+ if (!isProcessRunning(pid)) {
236
+ await fs.unlink(lockPath).catch(() => {});
237
+ continue;
238
+ }
239
+
240
+ lockOwners.push({
241
+ pid,
242
+ workspace: typeof lock?.workspace === 'string' ? lock.workspace : null,
243
+ cacheDirectory,
244
+ });
245
+ }
246
+
247
+ const killed = [];
248
+ const failed = [];
249
+ for (const owner of lockOwners) {
250
+ const terminated = await terminateProcess(owner.pid);
251
+ if (terminated) {
252
+ killed.push(owner);
253
+ } else {
254
+ failed.push(owner);
255
+ }
256
+ }
257
+
258
+ return { killed, failed };
259
+ }