@softerist/heuristic-mcp 3.0.15 → 3.0.16

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.
Files changed (49) hide show
  1. package/README.md +104 -104
  2. package/config.jsonc +173 -173
  3. package/features/ann-config.js +131 -0
  4. package/features/clear-cache.js +84 -0
  5. package/features/find-similar-code.js +291 -0
  6. package/features/hybrid-search.js +544 -0
  7. package/features/index-codebase.js +3268 -0
  8. package/features/lifecycle.js +1189 -0
  9. package/features/package-version.js +302 -0
  10. package/features/register.js +408 -0
  11. package/features/resources.js +156 -0
  12. package/features/set-workspace.js +265 -0
  13. package/index.js +96 -96
  14. package/lib/cache-ops.js +22 -22
  15. package/lib/cache-utils.js +565 -565
  16. package/lib/cache.js +1870 -1870
  17. package/lib/call-graph.js +396 -396
  18. package/lib/cli.js +1 -1
  19. package/lib/config.js +517 -517
  20. package/lib/constants.js +39 -39
  21. package/lib/embed-query-process.js +7 -7
  22. package/lib/embedding-process.js +7 -7
  23. package/lib/embedding-worker.js +299 -299
  24. package/lib/ignore-patterns.js +316 -316
  25. package/lib/json-worker.js +14 -14
  26. package/lib/json-writer.js +337 -337
  27. package/lib/logging.js +164 -164
  28. package/lib/memory-logger.js +13 -13
  29. package/lib/onnx-backend.js +193 -193
  30. package/lib/project-detector.js +84 -84
  31. package/lib/server-lifecycle.js +165 -165
  32. package/lib/settings-editor.js +754 -754
  33. package/lib/tokenizer.js +256 -256
  34. package/lib/utils.js +428 -428
  35. package/lib/vector-store-binary.js +627 -627
  36. package/lib/vector-store-sqlite.js +95 -95
  37. package/lib/workspace-env.js +28 -28
  38. package/mcp_config.json +9 -9
  39. package/package.json +86 -75
  40. package/scripts/clear-cache.js +20 -0
  41. package/scripts/download-model.js +43 -0
  42. package/scripts/mcp-launcher.js +49 -0
  43. package/scripts/postinstall.js +12 -0
  44. package/search-configs.js +36 -36
  45. package/.prettierrc +0 -7
  46. package/debug-pids.js +0 -30
  47. package/eslint.config.js +0 -36
  48. package/specs/plan.md +0 -23
  49. package/vitest.config.js +0 -39
@@ -0,0 +1,1189 @@
1
+ import { exec } from 'child_process';
2
+ import util from 'util';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import fs from 'fs/promises';
6
+ import fsSync from 'fs';
7
+ import { loadConfig } from '../lib/config.js';
8
+ import { getLogFilePath } from '../lib/logging.js';
9
+ import { clearStaleCaches } from '../lib/cache-utils.js';
10
+ import {
11
+ findMcpServerEntry,
12
+ parseJsonc,
13
+ setMcpServerDisabledInToml,
14
+ upsertMcpServerEntryInText,
15
+ } from '../lib/settings-editor.js';
16
+
17
+ const execPromise = util.promisify(exec);
18
+ const PID_FILE_NAME = '.heuristic-mcp.pid';
19
+
20
+ function getUserHomeDir() {
21
+ if (process.platform === 'win32' && process.env.USERPROFILE) {
22
+ return process.env.USERPROFILE;
23
+ }
24
+ return os.homedir();
25
+ }
26
+
27
+ async function listPidFilePaths() {
28
+ const pidFiles = new Set();
29
+ pidFiles.add(path.join(getUserHomeDir(), PID_FILE_NAME));
30
+ const globalCacheRoot = path.join(getGlobalCacheDir(), 'heuristic-mcp');
31
+ let cacheDirs = [];
32
+ try {
33
+ cacheDirs = await fs.readdir(globalCacheRoot);
34
+ } catch {
35
+ cacheDirs = [];
36
+ }
37
+ if (!Array.isArray(cacheDirs)) {
38
+ cacheDirs = [];
39
+ }
40
+ for (const dir of cacheDirs) {
41
+ pidFiles.add(path.join(globalCacheRoot, dir, PID_FILE_NAME));
42
+ }
43
+ return Array.from(pidFiles);
44
+ }
45
+
46
+ async function readPidFromFile(filePath) {
47
+ try {
48
+ const raw = await fs.readFile(filePath, 'utf-8');
49
+ const trimmed = String(raw || '').trim();
50
+ if (!trimmed) return null;
51
+ if (trimmed.startsWith('{')) {
52
+ try {
53
+ const parsed = JSON.parse(trimmed);
54
+ const pid = Number(parsed?.pid);
55
+ if (Number.isInteger(pid)) return pid;
56
+ } catch {
57
+ // fall through
58
+ }
59
+ }
60
+ const pid = parseInt(trimmed, 10);
61
+ if (!Number.isNaN(pid)) return pid;
62
+ } catch {
63
+ // ignore missing/invalid pid file
64
+ }
65
+ return null;
66
+ }
67
+
68
+ export async function stop() {
69
+ console.info('[Lifecycle] Stopping Heuristic MCP servers...');
70
+ try {
71
+ const platform = process.platform;
72
+ const currentPid = process.pid;
73
+ let pids = [];
74
+ const cmdByPid = new Map();
75
+ const manualPid = process.env.HEURISTIC_MCP_PID;
76
+
77
+ if (platform === 'win32') {
78
+ // 1. Try PID files first for reliability (per-workspace)
79
+ const pidFiles = await listPidFilePaths();
80
+ for (const pidFile of pidFiles) {
81
+ const pid = await readPidFromFile(pidFile);
82
+ if (!Number.isInteger(pid) || pid === currentPid) continue;
83
+ try {
84
+ process.kill(pid, 0);
85
+ const pidValue = String(pid);
86
+ if (!pids.includes(pidValue)) pids.push(pidValue);
87
+ } catch (e) {
88
+ // If we lack permission, still attempt to stop by PID.
89
+ if (e.code === 'EPERM') {
90
+ const pidValue = String(pid);
91
+ if (!pids.includes(pidValue)) pids.push(pidValue);
92
+ } else {
93
+ await fs.unlink(pidFile).catch(() => {});
94
+ }
95
+ }
96
+ }
97
+
98
+ // 2. Fallback to WMIC when CIM access is denied
99
+ if (pids.length === 0) {
100
+ try {
101
+ const { stdout } = await execPromise(
102
+ `wmic process where "CommandLine like '%heuristic-mcp%'" get ProcessId /FORMAT:LIST`
103
+ );
104
+ const matches = stdout.match(/ProcessId=(\d+)/g) || [];
105
+ for (const match of matches) {
106
+ const pid = match.replace('ProcessId=', '');
107
+ if (pid && !isNaN(pid) && parseInt(pid, 10) !== currentPid) {
108
+ if (!pids.includes(pid)) pids.push(pid);
109
+ }
110
+ }
111
+ } catch (_wmicErr) {
112
+ // ignore secondary failures
113
+ }
114
+ }
115
+
116
+ // 3. Fallback to process list with fuzzier matching (kill all heuristic-mcp instances)
117
+ try {
118
+ const { stdout } = await execPromise(
119
+ `powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and ($_.CommandLine -like '*heuristic-mcp*' -or $_.CommandLine -like '*heuristic-mcp\\\\index.js*' -or $_.CommandLine -like '*heuristic-mcp/index.js*') } | Select-Object -ExpandProperty ProcessId"`
120
+ );
121
+ const listPids = stdout
122
+ .trim()
123
+ .split(/\s+/)
124
+ .filter((p) => p && !isNaN(p) && parseInt(p) !== currentPid);
125
+
126
+ // Retrieve command lines to filter out workers
127
+ if (listPids.length > 0) {
128
+ const { stdout: cmdOut } = await execPromise(
129
+ `powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -in @(${listPids.join(',')}) } | Select-Object ProcessId, CommandLine"`
130
+ );
131
+ const lines = cmdOut.trim().split(/\r?\n/);
132
+ for (const line of lines) {
133
+ const trimmed = line.trim();
134
+ if (!trimmed || trimmed.startsWith('ProcessId')) continue;
135
+ const match = trimmed.match(/^(\d+)\s+(.*)$/);
136
+ if (match) {
137
+ const pid = parseInt(match[1], 10);
138
+ const cmd = match[2];
139
+ if (
140
+ cmd.includes('embedding-worker') ||
141
+ cmd.includes('embedding-process') ||
142
+ cmd.includes('json-worker')
143
+ ) {
144
+ continue;
145
+ }
146
+ if (pid && !pids.includes(String(pid))) {
147
+ pids.push(String(pid));
148
+ }
149
+ }
150
+ }
151
+ }
152
+ } catch (_e) {
153
+ /* ignore */
154
+ }
155
+ } else {
156
+ // Unix: Use pgrep to get all matching PIDs
157
+ try {
158
+ const { stdout } = await execPromise(`pgrep -fl "heuristic-mcp"`);
159
+ const lines = stdout.trim().split(/\r?\n/);
160
+
161
+ // Filter out current PID, dead processes, and workers
162
+ pids = [];
163
+ for (const line of lines) {
164
+ const tokens = line.trim().split(/\s+/).filter(Boolean);
165
+ if (tokens.length === 0) continue;
166
+
167
+ const allNumeric = tokens.every((token) => /^\d+$/.test(token));
168
+ const candidatePids = allNumeric ? tokens : [tokens[0]];
169
+
170
+ for (const candidate of candidatePids) {
171
+ const pid = parseInt(candidate, 10);
172
+ if (!Number.isFinite(pid) || pid === currentPid) continue;
173
+
174
+ // Exclude workers when command line is present
175
+ if (
176
+ !allNumeric &&
177
+ (line.includes('embedding-worker') ||
178
+ line.includes('embedding-process') ||
179
+ line.includes('json-worker'))
180
+ ) {
181
+ continue;
182
+ }
183
+
184
+ try {
185
+ process.kill(pid, 0);
186
+ const pidValue = String(pid);
187
+ if (!pids.includes(pidValue)) {
188
+ pids.push(pidValue);
189
+ }
190
+ } catch (_e) {
191
+ /* ignore */
192
+ }
193
+ }
194
+ }
195
+ } catch (e) {
196
+ // pgrep returns code 1 if no processes found, which is fine
197
+ if (e.code === 1) pids = [];
198
+ else throw e;
199
+ }
200
+ }
201
+
202
+ // Manual PID override (best-effort)
203
+ if (manualPid) {
204
+ const parts = String(manualPid)
205
+ .split(/[,\s]+/)
206
+ .map((part) => part.trim())
207
+ .filter(Boolean);
208
+ for (const part of parts) {
209
+ if (!isNaN(part)) {
210
+ const pidValue = String(parseInt(part, 10));
211
+ if (pidValue && !pids.includes(pidValue)) {
212
+ pids.push(pidValue);
213
+ }
214
+ }
215
+ }
216
+ }
217
+
218
+ if (pids.length === 0) {
219
+ console.info('[Lifecycle] No running instances found (already stopped).');
220
+ await setMcpServerEnabled(false);
221
+ return;
222
+ }
223
+
224
+ // Capture command lines before killing (best-effort)
225
+ try {
226
+ if (platform === 'win32') {
227
+ const { stdout } = await execPromise(
228
+ `powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -in @(${pids.join(',')}) } | Select-Object ProcessId, CommandLine"`
229
+ );
230
+ const lines = stdout.trim().split(/\r?\n/);
231
+ for (const line of lines) {
232
+ const trimmed = line.trim();
233
+ if (!trimmed || trimmed.startsWith('ProcessId')) continue;
234
+ const match = trimmed.match(/^(\d+)\s+(.*)$/);
235
+ if (match) {
236
+ cmdByPid.set(parseInt(match[1], 10), match[2]);
237
+ }
238
+ }
239
+ } else {
240
+ const { stdout } = await execPromise(`ps -o pid=,command= -p ${pids.join(',')}`);
241
+ const lines = stdout.trim().split(/\r?\n/);
242
+ for (const line of lines) {
243
+ const match = line.trim().match(/^(\d+)\s+(.*)$/);
244
+ if (match) {
245
+ cmdByPid.set(parseInt(match[1], 10), match[2]);
246
+ }
247
+ }
248
+ }
249
+ } catch (_e) {
250
+ // ignore command line lookup failures
251
+ }
252
+
253
+ // Kill each process (Windows uses taskkill for compatibility)
254
+ let killedCount = 0;
255
+ const killedPids = [];
256
+ const failedPids = [];
257
+ for (const pid of pids) {
258
+ try {
259
+ if (platform === 'win32') {
260
+ try {
261
+ await execPromise(`taskkill /PID ${pid} /T`);
262
+ } catch (e) {
263
+ const message = String(e?.message || '');
264
+ if (message.includes('not found') || message.includes('not be found')) {
265
+ // Process already exited; treat as success.
266
+ killedCount++;
267
+ killedPids.push(pid);
268
+ continue;
269
+ }
270
+ try {
271
+ await execPromise(`taskkill /PID ${pid} /T /F`);
272
+ } catch (e2) {
273
+ const message2 = String(e2?.message || '');
274
+ if (message2.includes('not found') || message2.includes('not be found')) {
275
+ killedCount++;
276
+ killedPids.push(pid);
277
+ continue;
278
+ }
279
+ throw e2;
280
+ }
281
+ }
282
+ } else {
283
+ process.kill(parseInt(pid), 'SIGTERM');
284
+ }
285
+ killedCount++;
286
+ killedPids.push(pid);
287
+ } catch (e) {
288
+ // Ignore if process already gone
289
+ if (e.code !== 'ESRCH') {
290
+ failedPids.push(pid);
291
+ console.warn(`[Lifecycle] Failed to kill PID ${pid}: ${e.message}`);
292
+ }
293
+ }
294
+ }
295
+
296
+ console.info(`[Lifecycle] ✅ Stopped ${killedCount} running instance(s).`);
297
+ if (killedPids.length > 0) {
298
+ console.info('[Lifecycle] Killed processes:');
299
+ for (const pid of killedPids) {
300
+ const cmd = cmdByPid.get(parseInt(pid, 10));
301
+ if (cmd) {
302
+ console.info(` ${pid}: ${cmd}`);
303
+ } else {
304
+ console.info(` ${pid}`);
305
+ }
306
+ }
307
+ }
308
+ if (failedPids.length > 0) {
309
+ console.info('[Lifecycle] Failed to kill:');
310
+ for (const pid of failedPids) {
311
+ const cmd = cmdByPid.get(parseInt(pid, 10));
312
+ if (cmd) {
313
+ console.info(` ${pid}: ${cmd}`);
314
+ } else {
315
+ console.info(` ${pid}`);
316
+ }
317
+ }
318
+ }
319
+
320
+ await setMcpServerEnabled(false);
321
+ } catch (error) {
322
+ console.warn(`[Lifecycle] Warning: Stop command encountered an error: ${error.message}`);
323
+ }
324
+ }
325
+
326
+ export async function start(filter = null) {
327
+ console.info('[Lifecycle] Ensuring server is configured...');
328
+ // Re-use the registration logic to ensure the config is present and correct
329
+ try {
330
+ const { register } = await import('./register.js');
331
+ await register(filter);
332
+ await setMcpServerEnabled(true);
333
+ console.info('[Lifecycle] ✅ Configuration checked.');
334
+ console.info(
335
+ '[Lifecycle] To start the server, please reload your IDE window or restart the IDE.'
336
+ );
337
+ } catch (err) {
338
+ console.error(`[Lifecycle] Failed to configure server: ${err.message}`);
339
+ }
340
+ }
341
+
342
+ async function setMcpServerEnabled(enabled) {
343
+ const paths = getMcpConfigPaths();
344
+ const target = 'heuristic-mcp';
345
+ let changed = 0;
346
+
347
+ for (const { name, path: configPath, format } of paths) {
348
+ try {
349
+ await fs.access(configPath);
350
+ } catch {
351
+ continue;
352
+ }
353
+
354
+ try {
355
+ const raw = await fs.readFile(configPath, 'utf-8');
356
+ if (!raw || !raw.trim()) {
357
+ continue;
358
+ }
359
+ if (format === 'toml') {
360
+ const updatedToml = setMcpServerDisabledInToml(raw, target, !enabled);
361
+ if (updatedToml === raw) {
362
+ continue;
363
+ }
364
+ await fs.writeFile(configPath, updatedToml);
365
+ changed++;
366
+ continue;
367
+ }
368
+
369
+ const parsed = parseJsonc(raw);
370
+ if (!parsed) {
371
+ console.warn(
372
+ `[Lifecycle] Skipping ${name} config: not valid JSON/JSONC (won't overwrite).`
373
+ );
374
+ continue;
375
+ }
376
+
377
+ const found = findMcpServerEntry(parsed, target);
378
+ if (!found || !found.entry || typeof found.entry !== 'object') {
379
+ continue;
380
+ }
381
+
382
+ const updatedEntry = { ...found.entry, disabled: !enabled };
383
+ const updatedText = upsertMcpServerEntryInText(raw, target, updatedEntry);
384
+ if (!updatedText) {
385
+ console.warn(`[Lifecycle] Failed to update ${name} config (unparseable layout).`);
386
+ continue;
387
+ }
388
+
389
+ await fs.writeFile(configPath, updatedText);
390
+ changed++;
391
+ } catch (err) {
392
+ console.warn(`[Lifecycle] Failed to update ${name} config: ${err.message}`);
393
+ }
394
+ }
395
+
396
+ if (changed > 0) {
397
+ console.info(
398
+ `[Lifecycle] MCP server ${enabled ? 'enabled' : 'disabled'} in ${changed} config file(s).`
399
+ );
400
+ }
401
+ }
402
+
403
+ function getMcpConfigPaths() {
404
+ const home = getUserHomeDir();
405
+ const configLocations = [
406
+ {
407
+ name: 'Antigravity',
408
+ path: path.join(home, '.gemini', 'antigravity', 'mcp_config.json'),
409
+ format: 'json',
410
+ },
411
+ {
412
+ name: 'Codex',
413
+ path: path.join(home, '.codex', 'config.toml'),
414
+ format: 'toml',
415
+ },
416
+ {
417
+ name: 'Claude Desktop',
418
+ path: path.join(home, '.config', 'Claude', 'claude_desktop_config.json'),
419
+ format: 'json',
420
+ },
421
+ {
422
+ name: 'VS Code',
423
+ path: path.join(home, '.config', 'Code', 'User', 'mcp.json'),
424
+ format: 'json',
425
+ },
426
+ {
427
+ name: 'VS Code Insiders',
428
+ path: path.join(home, '.config', 'Code - Insiders', 'User', 'mcp.json'),
429
+ format: 'json',
430
+ },
431
+ {
432
+ name: 'Cursor',
433
+ path: path.join(home, '.config', 'Cursor', 'User', 'settings.json'),
434
+ format: 'json',
435
+ },
436
+ {
437
+ name: 'Cursor Global',
438
+ path: path.join(home, '.cursor', 'mcp.json'),
439
+ format: 'json',
440
+ },
441
+ {
442
+ name: 'Windsurf',
443
+ path: path.join(home, '.codeium', 'windsurf', 'mcp_config.json'),
444
+ format: 'json',
445
+ },
446
+ {
447
+ name: 'Warp',
448
+ path: path.join(home, '.warp', 'mcp_settings.json'),
449
+ format: 'json',
450
+ },
451
+ ];
452
+
453
+ if (process.platform === 'darwin') {
454
+ configLocations[2].path = path.join(
455
+ home,
456
+ 'Library',
457
+ 'Application Support',
458
+ 'Claude',
459
+ 'claude_desktop_config.json'
460
+ );
461
+ configLocations[3].path = path.join(
462
+ home,
463
+ 'Library',
464
+ 'Application Support',
465
+ 'Code',
466
+ 'User',
467
+ 'mcp.json'
468
+ );
469
+ configLocations[4].path = path.join(
470
+ home,
471
+ 'Library',
472
+ 'Application Support',
473
+ 'Code - Insiders',
474
+ 'User',
475
+ 'mcp.json'
476
+ );
477
+ configLocations[5].path = path.join(
478
+ home,
479
+ 'Library',
480
+ 'Application Support',
481
+ 'Cursor',
482
+ 'User',
483
+ 'settings.json'
484
+ );
485
+ } else if (process.platform === 'win32') {
486
+ configLocations[2].path = path.join(
487
+ process.env.APPDATA || '',
488
+ 'Claude',
489
+ 'claude_desktop_config.json'
490
+ );
491
+ configLocations[3].path = path.join(process.env.APPDATA || '', 'Code', 'User', 'mcp.json');
492
+ configLocations[4].path = path.join(
493
+ process.env.APPDATA || '',
494
+ 'Code - Insiders',
495
+ 'User',
496
+ 'mcp.json'
497
+ );
498
+ configLocations[5].path = path.join(
499
+ process.env.APPDATA || '',
500
+ 'Cursor',
501
+ 'User',
502
+ 'settings.json'
503
+ );
504
+ configLocations.push({
505
+ name: 'Warp AppData',
506
+ path: path.join(process.env.APPDATA || '', 'Warp', 'mcp_settings.json'),
507
+ format: 'json',
508
+ });
509
+ }
510
+
511
+ return configLocations;
512
+ }
513
+
514
+ async function readTail(filePath, maxLines) {
515
+ const data = await fs.readFile(filePath, 'utf-8');
516
+ if (!data) return '';
517
+ const lines = data.split(/\r?\n/);
518
+ const tail = lines.slice(-maxLines).join('\n');
519
+ return tail.trimEnd();
520
+ }
521
+
522
+ async function followFile(filePath, startPosition) {
523
+ let position = startPosition;
524
+ const watcher = fsSync.watch(filePath, { persistent: true }, async (event) => {
525
+ if (event !== 'change') return;
526
+ try {
527
+ const stats = await fs.stat(filePath);
528
+ if (stats.size < position) {
529
+ position = 0;
530
+ }
531
+ if (stats.size === position) return;
532
+ const stream = fsSync.createReadStream(filePath, { start: position, end: stats.size - 1 });
533
+ stream.pipe(process.stdout, { end: false });
534
+ position = stats.size;
535
+ } catch {
536
+ // ignore read errors while watching
537
+ }
538
+ });
539
+
540
+ const stop = () => {
541
+ watcher.close();
542
+ process.exit(0);
543
+ };
544
+
545
+ process.on('SIGINT', stop);
546
+ process.on('SIGTERM', stop);
547
+ }
548
+
549
+ function formatDurationMs(ms) {
550
+ if (!Number.isFinite(ms) || ms < 0) return null;
551
+ const totalSeconds = Math.round(ms / 1000);
552
+ const hours = Math.floor(totalSeconds / 3600);
553
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
554
+ const seconds = totalSeconds % 60;
555
+
556
+ if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;
557
+ if (minutes > 0) return `${minutes}m ${seconds}s`;
558
+ return `${seconds}s`;
559
+ }
560
+
561
+ function formatDateTime(value) {
562
+ const date = value instanceof Date ? value : new Date(value);
563
+ if (Number.isNaN(date.getTime())) return null;
564
+ return `${date.toLocaleString()} (${date.toISOString()})`;
565
+ }
566
+
567
+ async function captureConsoleOutput(fn) {
568
+ const original = {
569
+ info: console.info,
570
+ warn: console.warn,
571
+ error: console.error,
572
+ };
573
+ const lines = [];
574
+ const collect = (...args) => {
575
+ const message = util.format(...args);
576
+ if (message && message.trim()) {
577
+ lines.push(message);
578
+ }
579
+ };
580
+ console.info = collect;
581
+ console.warn = collect;
582
+ console.error = collect;
583
+ try {
584
+ const result = await fn();
585
+ return { result, lines };
586
+ } finally {
587
+ console.info = original.info;
588
+ console.warn = original.warn;
589
+ console.error = original.error;
590
+ }
591
+ }
592
+
593
+ export async function logs({ workspaceDir = null, tailLines = 200, follow = true } = {}) {
594
+ const config = await loadConfig(workspaceDir);
595
+ const logPath = getLogFilePath(config);
596
+
597
+ try {
598
+ const stats = await fs.stat(logPath);
599
+ const tail = await readTail(logPath, tailLines);
600
+ if (tail) {
601
+ process.stdout.write(tail + '\n');
602
+ }
603
+
604
+ if (!follow) {
605
+ return;
606
+ }
607
+
608
+ console.info(`[Logs] Following ${logPath} (Ctrl+C to stop)...`);
609
+ await followFile(logPath, stats.size);
610
+ } catch (err) {
611
+ if (err.code === 'ENOENT') {
612
+ console.error(`[Logs] No log file found for workspace.`);
613
+ console.error(`[Logs] Expected location: ${logPath}`);
614
+ console.error(`[Logs] Start the server from your IDE, then run: heuristic-mcp --logs`);
615
+ return;
616
+ }
617
+ console.error(`[Logs] Failed to read log file: ${err.message}`);
618
+ }
619
+ }
620
+
621
+ // Helper to get global cache dir
622
+ function getGlobalCacheDir() {
623
+ const home = getUserHomeDir();
624
+ if (process.platform === 'win32') {
625
+ return process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
626
+ } else if (process.platform === 'darwin') {
627
+ return path.join(home, 'Library', 'Caches');
628
+ }
629
+ return process.env.XDG_CACHE_HOME || path.join(home, '.cache');
630
+ }
631
+
632
+ export async function status({ fix = false, cacheOnly = false, workspaceDir = null } = {}) {
633
+ try {
634
+ const pids = [];
635
+ const now = new Date();
636
+ const globalCacheRoot = path.join(getGlobalCacheDir(), 'heuristic-mcp');
637
+ let logPath = 'unknown';
638
+ let logStatus = '';
639
+ let cacheSummary = null;
640
+ let config = null;
641
+ let configLogs = [];
642
+
643
+ // 1. Check PID files first (per-workspace)
644
+ const pidFiles = await listPidFilePaths();
645
+ for (const pidFile of pidFiles) {
646
+ const pid = await readPidFromFile(pidFile);
647
+ if (!Number.isInteger(pid)) continue;
648
+ // Check if running
649
+ try {
650
+ process.kill(pid, 0);
651
+ pids.push(pid);
652
+ } catch (_e) {
653
+ // Stale PID file
654
+ await fs.unlink(pidFile).catch(() => {});
655
+ }
656
+ }
657
+
658
+ // 2. Fallback to process list if no PID file found or process dead
659
+ if (pids.length === 0) {
660
+ try {
661
+ const myPid = process.pid;
662
+ if (process.platform === 'win32') {
663
+ const { stdout } = await execPromise(
664
+ `powershell -NoProfile -Command "Get-CimInstance Win32_Process -Filter \\"CommandLine LIKE '%heuristic-mcp%index.js%'\\" | Select-Object -ExpandProperty ProcessId"`
665
+ );
666
+ const winPids = stdout
667
+ .trim()
668
+ .split(/\s+/)
669
+ .filter((p) => p && !isNaN(p));
670
+
671
+ // Retrieve command lines to filter out workers
672
+ if (winPids.length > 0) {
673
+ const { stdout: cmdOut } = await execPromise(
674
+ `powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -in @(${winPids.join(',')}) } | Select-Object ProcessId, CommandLine"`
675
+ );
676
+ const lines = cmdOut.trim().split(/\r?\n/);
677
+ for (const line of lines) {
678
+ const trimmed = line.trim();
679
+ if (!trimmed || trimmed.startsWith('ProcessId')) continue;
680
+ const match = trimmed.match(/^(\d+)\s+(.*)$/);
681
+ if (match) {
682
+ const pid = parseInt(match[1], 10);
683
+ const cmd = match[2];
684
+ if (
685
+ cmd.includes('embedding-worker') ||
686
+ cmd.includes('embedding-process') ||
687
+ cmd.includes('json-worker')
688
+ ) {
689
+ continue;
690
+ }
691
+ if (pid && pid !== myPid) {
692
+ if (!pids.includes(pid)) pids.push(pid);
693
+ }
694
+ }
695
+ }
696
+ }
697
+ } else {
698
+ const { stdout } = await execPromise('ps aux');
699
+ const lines = stdout.split('\n');
700
+ const validPids = [];
701
+
702
+ for (const line of lines) {
703
+ if (line.includes('heuristic-mcp/index.js') || line.includes('heuristic-mcp')) {
704
+ // Exclude workers
705
+ if (
706
+ line.includes('embedding-worker') ||
707
+ line.includes('embedding-process') ||
708
+ line.includes('json-worker')
709
+ ) {
710
+ continue;
711
+ }
712
+ const parts = line.trim().split(/\s+/);
713
+ const pid = parseInt(parts[1], 10);
714
+ if (pid && !isNaN(pid) && pid !== myPid && !line.includes(' grep ')) {
715
+ validPids.push(pid);
716
+ }
717
+ }
718
+ }
719
+ // Merge validPids into pids if not already present
720
+ for (const p of validPids) {
721
+ if (!pids.includes(p)) pids.push(p);
722
+ }
723
+ }
724
+ } catch (_e) {
725
+ /* ignore */
726
+ }
727
+ }
728
+
729
+ if (!cacheOnly) {
730
+ // STATUS OUTPUT
731
+ console.info(''); // spacer
732
+ if (pids.length > 0) {
733
+ console.info(`[Lifecycle] 🟢 Server is RUNNING. PID(s): ${pids.join(', ')}`);
734
+ } else {
735
+ console.info('[Lifecycle] ⚪ Server is STOPPED.');
736
+ }
737
+ if (pids.length > 1) {
738
+ console.info('[Lifecycle] ⚠️ Multiple servers detected; progress may be inconsistent.');
739
+ }
740
+ if (pids.length > 0) {
741
+ const cmdByPid = new Map();
742
+ try {
743
+ if (process.platform === 'win32') {
744
+ const { stdout } = await execPromise(
745
+ `powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -in @(${pids.join(',')}) } | Select-Object ProcessId, CommandLine"`
746
+ );
747
+ const lines = stdout.trim().split(/\r?\n/);
748
+ for (const line of lines) {
749
+ const trimmed = line.trim();
750
+ if (!trimmed || trimmed.startsWith('ProcessId')) continue;
751
+ const match = trimmed.match(/^(\d+)\s+(.*)$/);
752
+ if (match) {
753
+ cmdByPid.set(parseInt(match[1], 10), match[2]);
754
+ }
755
+ }
756
+ } else {
757
+ const { stdout } = await execPromise(`ps -o pid=,command= -p ${pids.join(',')}`);
758
+ const lines = stdout.trim().split(/\r?\n/);
759
+ for (const line of lines) {
760
+ const match = line.trim().match(/^(\d+)\s+(.*)$/);
761
+ if (match) {
762
+ cmdByPid.set(parseInt(match[1], 10), match[2]);
763
+ }
764
+ }
765
+ }
766
+ } catch (_e) {
767
+ // ignore command line lookup failures
768
+ }
769
+ if (cmdByPid.size > 0) {
770
+ console.info('[Lifecycle] Active command lines:');
771
+ for (const pid of pids) {
772
+ const cmd = cmdByPid.get(pid);
773
+ if (cmd) {
774
+ console.info(` ${pid}: ${cmd}`);
775
+ }
776
+ }
777
+ }
778
+ }
779
+ console.info(''); // spacer
780
+ } // End if (!cacheOnly) - server status
781
+
782
+ if (!cacheOnly) {
783
+ try {
784
+ const captured = await captureConsoleOutput(() => loadConfig(workspaceDir));
785
+ config = captured.result;
786
+ configLogs = captured.lines;
787
+ logPath = getLogFilePath(config);
788
+ try {
789
+ await fs.access(logPath);
790
+ logStatus = '(exists)';
791
+ } catch {
792
+ logStatus = '(not found)';
793
+ }
794
+ if (config?.cacheDirectory) {
795
+ const metaFile = path.join(config.cacheDirectory, 'meta.json');
796
+ const progressFile = path.join(config.cacheDirectory, 'progress.json');
797
+ let metaData = null;
798
+ let progressData = null;
799
+ try {
800
+ metaData = JSON.parse(await fs.readFile(metaFile, 'utf-8'));
801
+ } catch {
802
+ metaData = null;
803
+ }
804
+ try {
805
+ progressData = JSON.parse(await fs.readFile(progressFile, 'utf-8'));
806
+ } catch {
807
+ progressData = null;
808
+ }
809
+ cacheSummary = {
810
+ cacheDir: config.cacheDirectory,
811
+ hasSnapshot: !!metaData,
812
+ snapshotTime: metaData?.lastSaveTime || null,
813
+ progress: progressData && typeof progressData.progress === 'number' ? progressData : null,
814
+ };
815
+ }
816
+ } catch {
817
+ logPath = 'unknown';
818
+ }
819
+
820
+ if (config?.searchDirectory) {
821
+ console.info(`[Lifecycle] Workspace: ${config.searchDirectory}`);
822
+ }
823
+ console.info(` Log file: ${logPath} ${logStatus}`.trimEnd());
824
+ if (cacheSummary?.cacheDir) {
825
+ const snapshotLabel = cacheSummary.hasSnapshot ? 'available' : 'none';
826
+ console.info(`[Cache] Snapshot: ${snapshotLabel}`);
827
+ if (cacheSummary.snapshotTime) {
828
+ console.info(
829
+ `[Cache] Snapshot saved: ${formatDateTime(cacheSummary.snapshotTime) || cacheSummary.snapshotTime}`
830
+ );
831
+ }
832
+ if (cacheSummary.progress) {
833
+ const progress = cacheSummary.progress;
834
+ console.info(
835
+ `[Cache] Progress: ${progress.progress}/${progress.total} (${progress.message || 'n/a'})`
836
+ );
837
+ } else {
838
+ console.info('[Cache] Progress: idle');
839
+ }
840
+ }
841
+ console.info(''); // spacer
842
+
843
+ if (configLogs.length > 0) {
844
+ for (const line of configLogs) {
845
+ console.info(line);
846
+ }
847
+ console.info(''); // spacer
848
+ }
849
+ }
850
+
851
+ if (cacheOnly) {
852
+ // APPEND LOGS INFO (Cache Status)
853
+ console.info('[Status] Inspecting cache status...\n');
854
+
855
+ if (fix) {
856
+ console.info('[Status] Fixing stale caches...\n');
857
+ await clearStaleCaches();
858
+ }
859
+
860
+ const cacheDirs = await fs.readdir(globalCacheRoot).catch(() => []);
861
+
862
+ if (cacheDirs.length === 0) {
863
+ console.info('[Status] No cache directories found.');
864
+ console.info(`[Status] Expected location: ${globalCacheRoot}`);
865
+ } else {
866
+ console.info(
867
+ `[Status] Found ${cacheDirs.length} cache director${cacheDirs.length === 1 ? 'y' : 'ies'} in ${globalCacheRoot}`
868
+ );
869
+
870
+ for (const dir of cacheDirs) {
871
+ const cacheDir = path.join(globalCacheRoot, dir);
872
+ const metaFile = path.join(cacheDir, 'meta.json');
873
+ const progressFile = path.join(cacheDir, 'progress.json');
874
+
875
+ console.info(`${'─'.repeat(60)}`);
876
+ console.info(`📁 Cache: ${dir}`);
877
+ console.info(` Path: ${cacheDir}`);
878
+
879
+ let metaData = null;
880
+ try {
881
+ metaData = JSON.parse(await fs.readFile(metaFile, 'utf-8'));
882
+
883
+ console.info(` Status: ✅ Valid cache`);
884
+ console.info(` Workspace: ${metaData.workspace || 'Unknown'}`);
885
+ console.info(` Files indexed: ${metaData.filesIndexed ?? 'N/A'}`);
886
+ console.info(` Chunks stored: ${metaData.chunksStored ?? 'N/A'}`);
887
+
888
+ if (Number.isFinite(metaData.lastDiscoveredFiles)) {
889
+ console.info(` Files discovered (last run): ${metaData.lastDiscoveredFiles}`);
890
+ }
891
+ if (Number.isFinite(metaData.lastFilesProcessed)) {
892
+ console.info(` Files processed (last run): ${metaData.lastFilesProcessed}`);
893
+ }
894
+ if (
895
+ Number.isFinite(metaData.lastDiscoveredFiles) &&
896
+ Number.isFinite(metaData.lastFilesProcessed)
897
+ ) {
898
+ const delta = metaData.lastDiscoveredFiles - metaData.lastFilesProcessed;
899
+ console.info(` Discovery delta (last run): ${delta >= 0 ? delta : 0}`);
900
+ }
901
+
902
+ if (metaData.lastSaveTime) {
903
+ const saveDate = new Date(metaData.lastSaveTime);
904
+ const ageMs = now - saveDate;
905
+ const ageHours = Math.floor(ageMs / (1000 * 60 * 60));
906
+ const ageMins = Math.floor((ageMs % (1000 * 60 * 60)) / (1000 * 60));
907
+ console.info(
908
+ ` Cached snapshot saved: ${formatDateTime(saveDate)} (${ageHours}h ${ageMins}m ago)`
909
+ );
910
+ const ageLabel = formatDurationMs(ageMs);
911
+ if (ageLabel) {
912
+ console.info(` Cached snapshot age: ${ageLabel}`);
913
+ }
914
+ console.info(` Initial index complete at: ${formatDateTime(saveDate)}`);
915
+ }
916
+ if (metaData.lastIndexStartedAt) {
917
+ console.info(` Last index started: ${formatDateTime(metaData.lastIndexStartedAt)}`);
918
+ }
919
+ if (metaData.lastIndexEndedAt) {
920
+ console.info(` Last index ended: ${formatDateTime(metaData.lastIndexEndedAt)}`);
921
+ }
922
+ if (Number.isFinite(metaData.indexDurationMs)) {
923
+ const duration = formatDurationMs(metaData.indexDurationMs);
924
+ if (duration) {
925
+ console.info(` Last full index duration: ${duration}`);
926
+ }
927
+ }
928
+ if (metaData.lastIndexMode) {
929
+ console.info(` Last index mode: ${String(metaData.lastIndexMode)}`);
930
+ }
931
+ if (Number.isFinite(metaData.lastBatchSize)) {
932
+ console.info(` Last batch size: ${metaData.lastBatchSize}`);
933
+ }
934
+ if (Number.isFinite(metaData.lastWorkerThreads)) {
935
+ console.info(` Last worker threads: ${metaData.lastWorkerThreads}`);
936
+ }
937
+ try {
938
+ const dirStats = await fs.stat(cacheDir);
939
+ console.info(` Cache dir last write: ${formatDateTime(dirStats.mtime)}`);
940
+ } catch {
941
+ // ignore cache dir stat errors
942
+ }
943
+
944
+ // Verify indexing completion
945
+ if (metaData.filesIndexed && metaData.filesIndexed > 0) {
946
+ console.info(` Cached index: ✅ COMPLETE (${metaData.filesIndexed} files)`);
947
+ } else if (metaData.filesIndexed === 0) {
948
+ console.info(` Cached index: ⚠️ NO FILES (check excludePatterns)`);
949
+ } else {
950
+ console.info(` Cached index: ⚠️ INCOMPLETE`);
951
+ }
952
+ } catch (err) {
953
+ if (err.code === 'ENOENT') {
954
+ try {
955
+ const stats = await fs.stat(cacheDir);
956
+ const ageMs = new Date() - stats.mtime;
957
+ if (ageMs < 10 * 60 * 1000) {
958
+ console.info(` Status: ⏳ Initializing / Indexing in progress...`);
959
+ console.info(` (Metadata file has not been written yet using ID ${dir})`);
960
+ console.info(' Initial index: ⏳ IN PROGRESS');
961
+ } else {
962
+ console.info(` Status: ⚠️ Incomplete cache (stale)`);
963
+ }
964
+ console.info(` Cache dir last write: ${stats.mtime.toLocaleString()}`);
965
+ } catch {
966
+ console.info(` Status: ❌ Invalid cache directory`);
967
+ }
968
+ } else {
969
+ console.info(` Status: ❌ Invalid or corrupted (${err.message})`);
970
+ }
971
+ }
972
+
973
+ // Show latest indexing progress if available
974
+ let progressData = null;
975
+ try {
976
+ progressData = JSON.parse(await fs.readFile(progressFile, 'utf-8'));
977
+ } catch {
978
+ // no progress file
979
+ }
980
+
981
+ if (progressData && typeof progressData.progress === 'number') {
982
+ const updatedAt = progressData.updatedAt
983
+ ? formatDateTime(progressData.updatedAt)
984
+ : 'Unknown';
985
+ const progressLabel = metaData
986
+ ? 'Incremental update (post-snapshot)'
987
+ : 'Initial index progress';
988
+ console.info(
989
+ ` ${progressLabel}: ${progressData.progress}/${progressData.total} (${progressData.message || 'n/a'})`
990
+ );
991
+ console.info(` Progress updated: ${updatedAt}`);
992
+
993
+ if (progressData.updatedAt) {
994
+ const updatedDate = new Date(progressData.updatedAt);
995
+ const ageMs = now - updatedDate;
996
+ const staleMs = 5 * 60 * 1000;
997
+ const ageLabel = formatDurationMs(ageMs);
998
+ if (ageLabel) {
999
+ console.info(` Progress age: ${ageLabel}`);
1000
+ }
1001
+ if (Number.isFinite(ageMs) && ageMs > staleMs) {
1002
+ const staleLabel = formatDurationMs(ageMs);
1003
+ console.info(` Progress stale: last update ${staleLabel} ago`);
1004
+ }
1005
+ }
1006
+
1007
+ if (progressData.updatedAt && metaData?.lastSaveTime) {
1008
+ const updatedDate = new Date(progressData.updatedAt);
1009
+ const saveDate = new Date(metaData.lastSaveTime);
1010
+ if (updatedDate > saveDate) {
1011
+ console.info(' Note: Incremental update in progress; cached snapshot may lag.');
1012
+ }
1013
+ }
1014
+ if (progressData.indexMode) {
1015
+ console.info(` Current index mode: ${String(progressData.indexMode)}`);
1016
+ }
1017
+ if (
1018
+ progressData.workerCircuitOpen &&
1019
+ Number.isFinite(progressData.workersDisabledUntil)
1020
+ ) {
1021
+ const remainingMs = progressData.workersDisabledUntil - Date.now();
1022
+ const remainingLabel = formatDurationMs(Math.max(0, remainingMs));
1023
+ console.info(` Workers paused: ${remainingLabel || '0s'} remaining`);
1024
+ console.info(
1025
+ ` Workers disabled until: ${formatDateTime(progressData.workersDisabledUntil)}`
1026
+ );
1027
+ }
1028
+ } else {
1029
+ if (metaData) {
1030
+ console.info(' Summary: Cached snapshot available; no update running.');
1031
+ } else {
1032
+ console.info(' Summary: No cached snapshot yet; indexing has not started.');
1033
+ }
1034
+ }
1035
+
1036
+ if (metaData && progressData && typeof progressData.progress === 'number') {
1037
+ console.info(' Indexing state: Cached snapshot available; incremental update running.');
1038
+ } else if (metaData) {
1039
+ console.info(' Indexing state: Cached snapshot available; idle.');
1040
+ } else if (progressData && typeof progressData.progress === 'number') {
1041
+ console.info(' Indexing state: Initial index in progress; no cached snapshot yet.');
1042
+ } else {
1043
+ console.info(' Indexing state: No cached snapshot; idle.');
1044
+ }
1045
+ }
1046
+ console.info(`${'─'.repeat(60)}`);
1047
+ }
1048
+ } else {
1049
+ if (fix) {
1050
+ const results = await clearStaleCaches();
1051
+ if (results.removed > 0) {
1052
+ console.info(
1053
+ `[Status] Cache cleanup removed ${results.removed} stale cache${results.removed === 1 ? '' : 's'}`
1054
+ );
1055
+ }
1056
+ }
1057
+ const cacheDirs = await fs.readdir(globalCacheRoot).catch(() => null);
1058
+ if (Array.isArray(cacheDirs)) {
1059
+ console.info(
1060
+ `[Status] Cache: ${cacheDirs.length} director${cacheDirs.length === 1 ? 'y' : 'ies'} in ${globalCacheRoot}`
1061
+ );
1062
+ } else {
1063
+ console.info(`[Status] Cache: ${globalCacheRoot} (not found)`);
1064
+ }
1065
+ }
1066
+
1067
+ // Show paths only for --status command
1068
+ if (!cacheOnly) {
1069
+ // SHOW PATHS
1070
+ console.info('\n[Paths] Important locations:');
1071
+
1072
+ // Global npm bin
1073
+ let npmBin = 'unknown';
1074
+ try {
1075
+ const { stdout } = await execPromise('npm config get prefix');
1076
+ npmBin = path.join(stdout.trim(), 'bin');
1077
+ } catch {
1078
+ /* ignore */
1079
+ }
1080
+ console.info(` 📦 Global npm bin: ${npmBin}`);
1081
+
1082
+ // Configs
1083
+ const configLocations = [
1084
+ {
1085
+ name: 'Antigravity',
1086
+ path: path.join(getUserHomeDir(), '.gemini', 'antigravity', 'mcp_config.json'),
1087
+ },
1088
+ {
1089
+ name: 'Codex',
1090
+ path: path.join(getUserHomeDir(), '.codex', 'config.toml'),
1091
+ },
1092
+ {
1093
+ name: 'Claude Desktop',
1094
+ path: path.join(getUserHomeDir(), '.config', 'Claude', 'claude_desktop_config.json'),
1095
+ },
1096
+ {
1097
+ name: 'VS Code',
1098
+ path: path.join(getUserHomeDir(), '.config', 'Code', 'User', 'mcp.json'),
1099
+ },
1100
+ {
1101
+ name: 'Cursor',
1102
+ path: path.join(getUserHomeDir(), '.config', 'Cursor', 'User', 'settings.json'),
1103
+ },
1104
+ {
1105
+ name: 'Cursor Global',
1106
+ path: path.join(getUserHomeDir(), '.cursor', 'mcp.json'),
1107
+ },
1108
+ {
1109
+ name: 'Windsurf',
1110
+ path: path.join(getUserHomeDir(), '.codeium', 'windsurf', 'mcp_config.json'),
1111
+ },
1112
+ {
1113
+ name: 'Warp',
1114
+ path: path.join(getUserHomeDir(), '.warp', 'mcp_settings.json'),
1115
+ },
1116
+ ];
1117
+
1118
+ // Platform specific logic for config paths
1119
+ if (process.platform === 'darwin') {
1120
+ configLocations[2].path = path.join(
1121
+ os.homedir(),
1122
+ // Keep platform-native macOS path behavior.
1123
+ // Home directory above is used only for Windows/Linux defaults.
1124
+ 'Library',
1125
+ 'Application Support',
1126
+ 'Claude',
1127
+ 'claude_desktop_config.json'
1128
+ );
1129
+ configLocations[3].path = path.join(
1130
+ os.homedir(),
1131
+ 'Library',
1132
+ 'Application Support',
1133
+ 'Code',
1134
+ 'User',
1135
+ 'mcp.json'
1136
+ );
1137
+ configLocations[4].path = path.join(
1138
+ os.homedir(),
1139
+ 'Library',
1140
+ 'Application Support',
1141
+ 'Cursor',
1142
+ 'User',
1143
+ 'settings.json'
1144
+ );
1145
+ } else if (process.platform === 'win32') {
1146
+ configLocations[2].path = path.join(
1147
+ process.env.APPDATA || '',
1148
+ 'Claude',
1149
+ 'claude_desktop_config.json'
1150
+ );
1151
+ configLocations[3].path = path.join(
1152
+ process.env.APPDATA || '',
1153
+ 'Code',
1154
+ 'User',
1155
+ 'mcp.json'
1156
+ );
1157
+ configLocations[4].path = path.join(
1158
+ process.env.APPDATA || '',
1159
+ 'Cursor',
1160
+ 'User',
1161
+ 'settings.json'
1162
+ );
1163
+ configLocations.push({
1164
+ name: 'Warp AppData',
1165
+ path: path.join(process.env.APPDATA || '', 'Warp', 'mcp_settings.json'),
1166
+ });
1167
+ }
1168
+
1169
+ console.info(' ⚙️ MCP configs:');
1170
+ for (const loc of configLocations) {
1171
+ let status = '(not found)';
1172
+ try {
1173
+ await fs.access(loc.path);
1174
+ status = '(exists)';
1175
+ } catch {
1176
+ /* ignore */
1177
+ }
1178
+ console.info(` - ${loc.name}: ${loc.path} ${status}`);
1179
+ }
1180
+
1181
+ console.info(` 📝 Log file: ${logPath} ${logStatus}`.trimEnd());
1182
+ console.info(` 💾 Cache root: ${globalCacheRoot}`);
1183
+ console.info(` 📁 Current dir: ${process.cwd()}`);
1184
+ console.info('');
1185
+ } // End if (!cacheOnly) - paths
1186
+ } catch (error) {
1187
+ console.error(`[Lifecycle] Failed to check status: ${error.message}`);
1188
+ }
1189
+ }