@softerist/heuristic-mcp 3.0.17 → 3.2.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.
- package/config.jsonc +23 -6
- package/features/ann-config.js +7 -14
- package/features/clear-cache.js +3 -3
- package/features/find-similar-code.js +17 -22
- package/features/hybrid-search.js +59 -67
- package/features/index-codebase.js +305 -268
- package/features/lifecycle.js +370 -176
- package/features/package-version.js +15 -26
- package/features/register.js +75 -57
- package/features/resources.js +21 -47
- package/features/set-workspace.js +31 -43
- package/index.js +912 -200
- package/lib/cache-utils.js +95 -99
- package/lib/cache.js +121 -166
- package/lib/cli.js +246 -238
- package/lib/config.js +232 -62
- package/lib/constants.js +22 -2
- package/lib/embed-query-process.js +13 -29
- package/lib/embedding-process.js +29 -19
- package/lib/embedding-worker.js +166 -149
- package/lib/ignore-patterns.js +39 -39
- package/lib/json-writer.js +7 -34
- package/lib/logging.js +52 -48
- package/lib/onnx-backend.js +4 -4
- package/lib/path-utils.js +4 -21
- package/lib/project-detector.js +3 -3
- package/lib/server-lifecycle.js +148 -35
- package/lib/settings-editor.js +25 -18
- package/lib/slice-normalize.js +6 -16
- package/lib/tokenizer.js +56 -109
- package/lib/utils.js +62 -81
- package/lib/vector-store-binary.js +7 -7
- package/lib/vector-store-sqlite.js +35 -67
- package/lib/workspace-cache-key.js +36 -0
- package/lib/workspace-env.js +55 -14
- package/package.json +86 -86
package/features/lifecycle.js
CHANGED
|
@@ -54,13 +54,13 @@ async function readPidFromFile(filePath) {
|
|
|
54
54
|
const pid = Number(parsed?.pid);
|
|
55
55
|
if (Number.isInteger(pid)) return pid;
|
|
56
56
|
} catch {
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
const pid = parseInt(trimmed, 10);
|
|
61
61
|
if (!Number.isNaN(pid)) return pid;
|
|
62
62
|
} catch {
|
|
63
|
-
|
|
63
|
+
|
|
64
64
|
}
|
|
65
65
|
return null;
|
|
66
66
|
}
|
|
@@ -75,7 +75,7 @@ export async function stop() {
|
|
|
75
75
|
const manualPid = process.env.HEURISTIC_MCP_PID;
|
|
76
76
|
|
|
77
77
|
if (platform === 'win32') {
|
|
78
|
-
|
|
78
|
+
|
|
79
79
|
const pidFiles = await listPidFilePaths();
|
|
80
80
|
for (const pidFile of pidFiles) {
|
|
81
81
|
const pid = await readPidFromFile(pidFile);
|
|
@@ -85,7 +85,7 @@ export async function stop() {
|
|
|
85
85
|
const pidValue = String(pid);
|
|
86
86
|
if (!pids.includes(pidValue)) pids.push(pidValue);
|
|
87
87
|
} catch (e) {
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
if (e.code === 'EPERM') {
|
|
90
90
|
const pidValue = String(pid);
|
|
91
91
|
if (!pids.includes(pidValue)) pids.push(pidValue);
|
|
@@ -95,35 +95,16 @@ export async function stop() {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
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
98
|
try {
|
|
118
99
|
const { stdout } = await execPromise(
|
|
119
|
-
`powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.
|
|
100
|
+
`powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.Name -match '^node(\\\\.exe)?$' -and $_.CommandLine -and ($_.CommandLine -like '*heuristic-mcp\\\\index.js*' -or $_.CommandLine -like '*heuristic-mcp/index.js*') } | Select-Object -ExpandProperty ProcessId"`
|
|
120
101
|
);
|
|
121
102
|
const listPids = stdout
|
|
122
103
|
.trim()
|
|
123
104
|
.split(/\s+/)
|
|
124
105
|
.filter((p) => p && !isNaN(p) && parseInt(p) !== currentPid);
|
|
125
106
|
|
|
126
|
-
|
|
107
|
+
|
|
127
108
|
if (listPids.length > 0) {
|
|
128
109
|
const { stdout: cmdOut } = await execPromise(
|
|
129
110
|
`powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -in @(${listPids.join(',')}) } | Select-Object ProcessId, CommandLine"`
|
|
@@ -150,15 +131,15 @@ export async function stop() {
|
|
|
150
131
|
}
|
|
151
132
|
}
|
|
152
133
|
} catch (_e) {
|
|
153
|
-
|
|
134
|
+
|
|
154
135
|
}
|
|
155
136
|
} else {
|
|
156
|
-
|
|
137
|
+
|
|
157
138
|
try {
|
|
158
139
|
const { stdout } = await execPromise(`pgrep -fl "heuristic-mcp"`);
|
|
159
140
|
const lines = stdout.trim().split(/\r?\n/);
|
|
160
141
|
|
|
161
|
-
|
|
142
|
+
|
|
162
143
|
pids = [];
|
|
163
144
|
for (const line of lines) {
|
|
164
145
|
const tokens = line.trim().split(/\s+/).filter(Boolean);
|
|
@@ -171,7 +152,7 @@ export async function stop() {
|
|
|
171
152
|
const pid = parseInt(candidate, 10);
|
|
172
153
|
if (!Number.isFinite(pid) || pid === currentPid) continue;
|
|
173
154
|
|
|
174
|
-
|
|
155
|
+
|
|
175
156
|
if (
|
|
176
157
|
!allNumeric &&
|
|
177
158
|
(line.includes('embedding-worker') ||
|
|
@@ -188,18 +169,18 @@ export async function stop() {
|
|
|
188
169
|
pids.push(pidValue);
|
|
189
170
|
}
|
|
190
171
|
} catch (_e) {
|
|
191
|
-
|
|
172
|
+
|
|
192
173
|
}
|
|
193
174
|
}
|
|
194
175
|
}
|
|
195
176
|
} catch (e) {
|
|
196
|
-
|
|
177
|
+
|
|
197
178
|
if (e.code === 1) pids = [];
|
|
198
179
|
else throw e;
|
|
199
180
|
}
|
|
200
181
|
}
|
|
201
182
|
|
|
202
|
-
|
|
183
|
+
|
|
203
184
|
if (manualPid) {
|
|
204
185
|
const parts = String(manualPid)
|
|
205
186
|
.split(/[,\s]+/)
|
|
@@ -221,7 +202,7 @@ export async function stop() {
|
|
|
221
202
|
return;
|
|
222
203
|
}
|
|
223
204
|
|
|
224
|
-
|
|
205
|
+
|
|
225
206
|
try {
|
|
226
207
|
if (platform === 'win32') {
|
|
227
208
|
const { stdout } = await execPromise(
|
|
@@ -247,10 +228,10 @@ export async function stop() {
|
|
|
247
228
|
}
|
|
248
229
|
}
|
|
249
230
|
} catch (_e) {
|
|
250
|
-
|
|
231
|
+
|
|
251
232
|
}
|
|
252
233
|
|
|
253
|
-
|
|
234
|
+
|
|
254
235
|
let killedCount = 0;
|
|
255
236
|
const killedPids = [];
|
|
256
237
|
const failedPids = [];
|
|
@@ -262,7 +243,7 @@ export async function stop() {
|
|
|
262
243
|
} catch (e) {
|
|
263
244
|
const message = String(e?.message || '');
|
|
264
245
|
if (message.includes('not found') || message.includes('not be found')) {
|
|
265
|
-
|
|
246
|
+
|
|
266
247
|
killedCount++;
|
|
267
248
|
killedPids.push(pid);
|
|
268
249
|
continue;
|
|
@@ -285,7 +266,7 @@ export async function stop() {
|
|
|
285
266
|
killedCount++;
|
|
286
267
|
killedPids.push(pid);
|
|
287
268
|
} catch (e) {
|
|
288
|
-
|
|
269
|
+
|
|
289
270
|
if (e.code !== 'ESRCH') {
|
|
290
271
|
failedPids.push(pid);
|
|
291
272
|
console.warn(`[Lifecycle] Failed to kill PID ${pid}: ${e.message}`);
|
|
@@ -325,7 +306,7 @@ export async function stop() {
|
|
|
325
306
|
|
|
326
307
|
export async function start(filter = null) {
|
|
327
308
|
console.info('[Lifecycle] Ensuring server is configured...');
|
|
328
|
-
|
|
309
|
+
|
|
329
310
|
try {
|
|
330
311
|
const { register } = await import('./register.js');
|
|
331
312
|
await register(filter);
|
|
@@ -379,8 +360,13 @@ async function setMcpServerEnabled(enabled) {
|
|
|
379
360
|
continue;
|
|
380
361
|
}
|
|
381
362
|
|
|
382
|
-
const updatedEntry = { ...found.entry
|
|
383
|
-
|
|
363
|
+
const updatedEntry = { ...found.entry };
|
|
364
|
+
if (enabled) {
|
|
365
|
+
delete updatedEntry.disabled;
|
|
366
|
+
} else {
|
|
367
|
+
updatedEntry.disabled = true;
|
|
368
|
+
}
|
|
369
|
+
const updatedText = upsertMcpServerEntryInText(raw, target, updatedEntry);
|
|
384
370
|
if (!updatedText) {
|
|
385
371
|
console.warn(`[Lifecycle] Failed to update ${name} config (unparseable layout).`);
|
|
386
372
|
continue;
|
|
@@ -533,7 +519,7 @@ async function followFile(filePath, startPosition) {
|
|
|
533
519
|
stream.pipe(process.stdout, { end: false });
|
|
534
520
|
position = stats.size;
|
|
535
521
|
} catch {
|
|
536
|
-
|
|
522
|
+
|
|
537
523
|
}
|
|
538
524
|
});
|
|
539
525
|
|
|
@@ -558,13 +544,165 @@ function formatDurationMs(ms) {
|
|
|
558
544
|
return `${seconds}s`;
|
|
559
545
|
}
|
|
560
546
|
|
|
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
|
-
|
|
547
|
+
function formatDateTime(value) {
|
|
548
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
549
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
550
|
+
return `${date.toLocaleString()} (${date.toISOString()})`;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function parseFileProgressSummary(progressData) {
|
|
554
|
+
const message = String(progressData?.message || '');
|
|
555
|
+
if (!message) return null;
|
|
556
|
+
|
|
557
|
+
const indexedMatch = message.match(/Indexed\s+(\d+)\s*\/\s*(\d+)\s+files/i);
|
|
558
|
+
if (indexedMatch) {
|
|
559
|
+
return {
|
|
560
|
+
indexed: Number(indexedMatch[1]),
|
|
561
|
+
total: Number(indexedMatch[2]),
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const completeMatch = message.match(/Complete:\s+\d+\s+chunks\s+from\s+(\d+)\s+files/i);
|
|
566
|
+
if (completeMatch) {
|
|
567
|
+
const total = Number(completeMatch[1]);
|
|
568
|
+
return { indexed: total, total };
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const processingMatch = message.match(/Processing\s+(\d+)\s+changed files/i);
|
|
572
|
+
if (processingMatch) {
|
|
573
|
+
return { indexed: null, total: Number(processingMatch[1]) };
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return null;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function normalizePathForCompare(targetPath) {
|
|
580
|
+
if (!targetPath) return '';
|
|
581
|
+
const resolved = path.resolve(targetPath);
|
|
582
|
+
return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function extractWorkspaceFromCommandLine(commandLine) {
|
|
586
|
+
if (!commandLine || typeof commandLine !== 'string') return null;
|
|
587
|
+
|
|
588
|
+
const regex = /--workspace(?:=|\s+)("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\S+)/g;
|
|
589
|
+
let match;
|
|
590
|
+
while ((match = regex.exec(commandLine)) !== null) {
|
|
591
|
+
let candidate = match[1] || '';
|
|
592
|
+
if (
|
|
593
|
+
(candidate.startsWith('"') && candidate.endsWith('"')) ||
|
|
594
|
+
(candidate.startsWith("'") && candidate.endsWith("'"))
|
|
595
|
+
) {
|
|
596
|
+
candidate = candidate.slice(1, -1);
|
|
597
|
+
}
|
|
598
|
+
if (!candidate || candidate.includes('${')) continue;
|
|
599
|
+
return candidate;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
async function collectRuntimeByPid({ pids, globalCacheRoot }) {
|
|
606
|
+
const pidSet = new Set((Array.isArray(pids) ? pids : []).map((pid) => Number(pid)));
|
|
607
|
+
const runtimeByPid = new Map();
|
|
608
|
+
if (pidSet.size === 0) return runtimeByPid;
|
|
609
|
+
|
|
610
|
+
const cacheDirs = await fs.readdir(globalCacheRoot).catch(() => []);
|
|
611
|
+
if (!Array.isArray(cacheDirs)) return runtimeByPid;
|
|
612
|
+
|
|
613
|
+
for (const dir of cacheDirs) {
|
|
614
|
+
const cacheDirectory = path.join(globalCacheRoot, dir);
|
|
615
|
+
const lockPath = path.join(cacheDirectory, 'server.lock.json');
|
|
616
|
+
const localPidPath = path.join(cacheDirectory, PID_FILE_NAME);
|
|
617
|
+
const logFile = path.join(cacheDirectory, 'logs', 'server.log');
|
|
618
|
+
const metaPath = path.join(cacheDirectory, 'meta.json');
|
|
619
|
+
|
|
620
|
+
let lockData = null;
|
|
621
|
+
try {
|
|
622
|
+
lockData = JSON.parse(await fs.readFile(lockPath, 'utf-8'));
|
|
623
|
+
} catch {
|
|
624
|
+
lockData = null;
|
|
625
|
+
}
|
|
626
|
+
const lockPid = Number(lockData?.pid);
|
|
627
|
+
if (Number.isInteger(lockPid) && pidSet.has(lockPid)) {
|
|
628
|
+
runtimeByPid.set(lockPid, {
|
|
629
|
+
pid: lockPid,
|
|
630
|
+
cacheDirectory,
|
|
631
|
+
workspace:
|
|
632
|
+
typeof lockData?.workspace === 'string' && lockData.workspace.trim()
|
|
633
|
+
? lockData.workspace
|
|
634
|
+
: null,
|
|
635
|
+
workspaceSource: 'lock',
|
|
636
|
+
logFile,
|
|
637
|
+
});
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const pidFromCache = await readPidFromFile(localPidPath);
|
|
642
|
+
if (!Number.isInteger(pidFromCache) || !pidSet.has(pidFromCache)) {
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
if (runtimeByPid.has(pidFromCache)) {
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
let metaWorkspace = null;
|
|
650
|
+
try {
|
|
651
|
+
const metaData = JSON.parse(await fs.readFile(metaPath, 'utf-8'));
|
|
652
|
+
if (typeof metaData?.workspace === 'string' && metaData.workspace.trim()) {
|
|
653
|
+
metaWorkspace = metaData.workspace;
|
|
654
|
+
}
|
|
655
|
+
} catch {
|
|
656
|
+
metaWorkspace = null;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
runtimeByPid.set(pidFromCache, {
|
|
660
|
+
pid: pidFromCache,
|
|
661
|
+
cacheDirectory,
|
|
662
|
+
workspace: metaWorkspace,
|
|
663
|
+
workspaceSource: metaWorkspace ? 'meta' : 'cache',
|
|
664
|
+
logFile,
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
return runtimeByPid;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function selectRuntimeForStatus({ pids, runtimeByPid, requestedWorkspace }) {
|
|
672
|
+
if (!Array.isArray(pids) || pids.length === 0) return null;
|
|
673
|
+
if (!(runtimeByPid instanceof Map) || runtimeByPid.size === 0) return null;
|
|
674
|
+
|
|
675
|
+
if (requestedWorkspace) {
|
|
676
|
+
const requestedNormalized = normalizePathForCompare(requestedWorkspace);
|
|
677
|
+
for (const pid of pids) {
|
|
678
|
+
const runtime = runtimeByPid.get(pid);
|
|
679
|
+
if (!runtime?.workspace) continue;
|
|
680
|
+
if (normalizePathForCompare(runtime.workspace) === requestedNormalized) {
|
|
681
|
+
return runtime;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (pids.length === 1) {
|
|
687
|
+
return runtimeByPid.get(pids[0]) || null;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
for (const pid of pids) {
|
|
691
|
+
const runtime = runtimeByPid.get(pid);
|
|
692
|
+
if (runtime?.workspace) {
|
|
693
|
+
return runtime;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
for (const pid of pids) {
|
|
698
|
+
const runtime = runtimeByPid.get(pid);
|
|
699
|
+
if (runtime) return runtime;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return null;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
async function captureConsoleOutput(fn) {
|
|
568
706
|
const original = {
|
|
569
707
|
info: console.info,
|
|
570
708
|
warn: console.warn,
|
|
@@ -629,46 +767,49 @@ function getGlobalCacheDir() {
|
|
|
629
767
|
return process.env.XDG_CACHE_HOME || path.join(home, '.cache');
|
|
630
768
|
}
|
|
631
769
|
|
|
632
|
-
export async function status({ fix = false, cacheOnly = false, workspaceDir = null } = {}) {
|
|
633
|
-
try {
|
|
634
|
-
const pids = [];
|
|
770
|
+
export async function status({ fix = false, cacheOnly = false, workspaceDir = null } = {}) {
|
|
771
|
+
try {
|
|
772
|
+
const pids = [];
|
|
635
773
|
const now = new Date();
|
|
636
774
|
const globalCacheRoot = path.join(getGlobalCacheDir(), 'heuristic-mcp');
|
|
637
775
|
let logPath = 'unknown';
|
|
638
776
|
let logStatus = '';
|
|
639
|
-
let cacheSummary = null;
|
|
640
|
-
let config = null;
|
|
641
|
-
let configLogs = [];
|
|
642
|
-
|
|
643
|
-
|
|
777
|
+
let cacheSummary = null;
|
|
778
|
+
let config = null;
|
|
779
|
+
let configLogs = [];
|
|
780
|
+
const cmdByPid = new Map();
|
|
781
|
+
let runtimeByPid = new Map();
|
|
782
|
+
let selectedRuntime = null;
|
|
783
|
+
|
|
784
|
+
|
|
644
785
|
const pidFiles = await listPidFilePaths();
|
|
645
786
|
for (const pidFile of pidFiles) {
|
|
646
787
|
const pid = await readPidFromFile(pidFile);
|
|
647
788
|
if (!Number.isInteger(pid)) continue;
|
|
648
|
-
|
|
789
|
+
|
|
649
790
|
try {
|
|
650
791
|
process.kill(pid, 0);
|
|
651
792
|
pids.push(pid);
|
|
652
793
|
} catch (_e) {
|
|
653
|
-
|
|
794
|
+
|
|
654
795
|
await fs.unlink(pidFile).catch(() => {});
|
|
655
796
|
}
|
|
656
797
|
}
|
|
657
798
|
|
|
658
|
-
|
|
799
|
+
|
|
659
800
|
if (pids.length === 0) {
|
|
660
801
|
try {
|
|
661
802
|
const myPid = process.pid;
|
|
662
803
|
if (process.platform === 'win32') {
|
|
663
804
|
const { stdout } = await execPromise(
|
|
664
|
-
`powershell -NoProfile -Command "Get-CimInstance Win32_Process -
|
|
805
|
+
`powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.Name -match '^node(\\\\.exe)?$' -and $_.CommandLine -and ($_.CommandLine -like '*heuristic-mcp\\\\index.js*' -or $_.CommandLine -like '*heuristic-mcp/index.js*') } | Select-Object -ExpandProperty ProcessId"`
|
|
665
806
|
);
|
|
666
807
|
const winPids = stdout
|
|
667
808
|
.trim()
|
|
668
809
|
.split(/\s+/)
|
|
669
810
|
.filter((p) => p && !isNaN(p));
|
|
670
811
|
|
|
671
|
-
|
|
812
|
+
|
|
672
813
|
if (winPids.length > 0) {
|
|
673
814
|
const { stdout: cmdOut } = await execPromise(
|
|
674
815
|
`powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -in @(${winPids.join(',')}) } | Select-Object ProcessId, CommandLine"`
|
|
@@ -701,7 +842,7 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
701
842
|
|
|
702
843
|
for (const line of lines) {
|
|
703
844
|
if (line.includes('heuristic-mcp/index.js') || line.includes('heuristic-mcp')) {
|
|
704
|
-
|
|
845
|
+
|
|
705
846
|
if (
|
|
706
847
|
line.includes('embedding-worker') ||
|
|
707
848
|
line.includes('embedding-process') ||
|
|
@@ -716,31 +857,30 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
716
857
|
}
|
|
717
858
|
}
|
|
718
859
|
}
|
|
719
|
-
|
|
860
|
+
|
|
720
861
|
for (const p of validPids) {
|
|
721
862
|
if (!pids.includes(p)) pids.push(p);
|
|
722
863
|
}
|
|
723
864
|
}
|
|
724
865
|
} catch (_e) {
|
|
725
|
-
|
|
866
|
+
|
|
726
867
|
}
|
|
727
868
|
}
|
|
728
869
|
|
|
729
870
|
if (!cacheOnly) {
|
|
730
|
-
|
|
731
|
-
console.info('');
|
|
871
|
+
|
|
872
|
+
console.info('');
|
|
732
873
|
if (pids.length > 0) {
|
|
733
874
|
console.info(`[Lifecycle] 🟢 Server is RUNNING. PID(s): ${pids.join(', ')}`);
|
|
734
875
|
} else {
|
|
735
876
|
console.info('[Lifecycle] ⚪ Server is STOPPED.');
|
|
736
877
|
}
|
|
737
|
-
if (pids.length > 1) {
|
|
738
|
-
console.info('[Lifecycle] ⚠️ Multiple servers detected; progress may be inconsistent.');
|
|
739
|
-
}
|
|
740
|
-
if (pids.length > 0) {
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
if (process.platform === 'win32') {
|
|
878
|
+
if (pids.length > 1) {
|
|
879
|
+
console.info('[Lifecycle] ⚠️ Multiple servers detected; progress may be inconsistent.');
|
|
880
|
+
}
|
|
881
|
+
if (pids.length > 0) {
|
|
882
|
+
try {
|
|
883
|
+
if (process.platform === 'win32') {
|
|
744
884
|
const { stdout } = await execPromise(
|
|
745
885
|
`powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -in @(${pids.join(',')}) } | Select-Object ProcessId, CommandLine"`
|
|
746
886
|
);
|
|
@@ -764,38 +904,56 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
764
904
|
}
|
|
765
905
|
}
|
|
766
906
|
} catch (_e) {
|
|
767
|
-
|
|
907
|
+
|
|
768
908
|
}
|
|
769
|
-
|
|
770
|
-
console.info('[Lifecycle] Active command lines:');
|
|
771
|
-
for (const pid of pids) {
|
|
909
|
+
if (cmdByPid.size > 0) {
|
|
910
|
+
console.info('[Lifecycle] Active command lines:');
|
|
911
|
+
for (const pid of pids) {
|
|
772
912
|
const cmd = cmdByPid.get(pid);
|
|
773
913
|
if (cmd) {
|
|
774
914
|
console.info(` ${pid}: ${cmd}`);
|
|
775
915
|
}
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
runtimeByPid = await collectRuntimeByPid({ pids, globalCacheRoot });
|
|
920
|
+
for (const [pid, runtime] of runtimeByPid.entries()) {
|
|
921
|
+
if (runtime?.workspace) continue;
|
|
922
|
+
const cmd = cmdByPid.get(pid);
|
|
923
|
+
const workspaceFromCmd = extractWorkspaceFromCommandLine(cmd);
|
|
924
|
+
if (workspaceFromCmd) {
|
|
925
|
+
runtime.workspace = workspaceFromCmd;
|
|
926
|
+
runtime.workspaceSource = 'cmd';
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
selectedRuntime = selectRuntimeForStatus({
|
|
930
|
+
pids,
|
|
931
|
+
runtimeByPid,
|
|
932
|
+
requestedWorkspace: workspaceDir,
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
console.info('');
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (!cacheOnly) {
|
|
939
|
+
try {
|
|
940
|
+
const configWorkspaceDir = workspaceDir || selectedRuntime?.workspace || null;
|
|
941
|
+
const captured = await captureConsoleOutput(() => loadConfig(configWorkspaceDir));
|
|
942
|
+
config = captured.result;
|
|
943
|
+
configLogs = captured.lines;
|
|
944
|
+
logPath = selectedRuntime?.logFile || getLogFilePath(config);
|
|
945
|
+
try {
|
|
946
|
+
await fs.access(logPath);
|
|
947
|
+
logStatus = '(exists)';
|
|
791
948
|
} catch {
|
|
792
949
|
logStatus = '(not found)';
|
|
793
950
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
const
|
|
797
|
-
|
|
798
|
-
let
|
|
951
|
+
const statusCacheDirectory = selectedRuntime?.cacheDirectory || config?.cacheDirectory;
|
|
952
|
+
if (statusCacheDirectory) {
|
|
953
|
+
const metaFile = path.join(statusCacheDirectory, 'meta.json');
|
|
954
|
+
const progressFile = path.join(statusCacheDirectory, 'progress.json');
|
|
955
|
+
let metaData = null;
|
|
956
|
+
let progressData = null;
|
|
799
957
|
try {
|
|
800
958
|
metaData = JSON.parse(await fs.readFile(metaFile, 'utf-8'));
|
|
801
959
|
} catch {
|
|
@@ -806,21 +964,27 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
806
964
|
} catch {
|
|
807
965
|
progressData = null;
|
|
808
966
|
}
|
|
809
|
-
cacheSummary = {
|
|
810
|
-
cacheDir:
|
|
811
|
-
hasSnapshot: !!metaData,
|
|
812
|
-
snapshotTime: metaData?.lastSaveTime || null,
|
|
813
|
-
progress: progressData && typeof progressData.progress === 'number' ? progressData : null,
|
|
814
|
-
};
|
|
815
|
-
}
|
|
967
|
+
cacheSummary = {
|
|
968
|
+
cacheDir: statusCacheDirectory,
|
|
969
|
+
hasSnapshot: !!metaData,
|
|
970
|
+
snapshotTime: metaData?.lastSaveTime || null,
|
|
971
|
+
progress: progressData && typeof progressData.progress === 'number' ? progressData : null,
|
|
972
|
+
};
|
|
973
|
+
}
|
|
816
974
|
} catch {
|
|
817
975
|
logPath = 'unknown';
|
|
818
976
|
}
|
|
819
977
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
978
|
+
const displayWorkspace = selectedRuntime?.workspace || config?.searchDirectory;
|
|
979
|
+
if (displayWorkspace) {
|
|
980
|
+
console.info(`[Lifecycle] Workspace: ${displayWorkspace}`);
|
|
981
|
+
}
|
|
982
|
+
if (selectedRuntime?.workspace && selectedRuntime.workspace !== config?.searchDirectory) {
|
|
983
|
+
console.info(
|
|
984
|
+
` Workspace source: running server (${selectedRuntime.workspaceSource || 'runtime'})`
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
console.info(` Log file: ${logPath} ${logStatus}`.trimEnd());
|
|
824
988
|
if (cacheSummary?.cacheDir) {
|
|
825
989
|
const snapshotLabel = cacheSummary.hasSnapshot ? 'available' : 'none';
|
|
826
990
|
console.info(`[Cache] Snapshot: ${snapshotLabel}`);
|
|
@@ -838,18 +1002,18 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
838
1002
|
console.info('[Cache] Progress: idle');
|
|
839
1003
|
}
|
|
840
1004
|
}
|
|
841
|
-
console.info('');
|
|
1005
|
+
console.info('');
|
|
842
1006
|
|
|
843
1007
|
if (configLogs.length > 0) {
|
|
844
1008
|
for (const line of configLogs) {
|
|
845
1009
|
console.info(line);
|
|
846
1010
|
}
|
|
847
|
-
console.info('');
|
|
1011
|
+
console.info('');
|
|
848
1012
|
}
|
|
849
1013
|
}
|
|
850
1014
|
|
|
851
1015
|
if (cacheOnly) {
|
|
852
|
-
|
|
1016
|
+
|
|
853
1017
|
console.info('[Status] Inspecting cache status...\n');
|
|
854
1018
|
|
|
855
1019
|
if (fix) {
|
|
@@ -867,15 +1031,28 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
867
1031
|
`[Status] Found ${cacheDirs.length} cache director${cacheDirs.length === 1 ? 'y' : 'ies'} in ${globalCacheRoot}`
|
|
868
1032
|
);
|
|
869
1033
|
|
|
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
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1034
|
+
for (const dir of cacheDirs) {
|
|
1035
|
+
const cacheDir = path.join(globalCacheRoot, dir);
|
|
1036
|
+
const metaFile = path.join(cacheDir, 'meta.json');
|
|
1037
|
+
const progressFile = path.join(cacheDir, 'progress.json');
|
|
1038
|
+
let progressData = null;
|
|
1039
|
+
try {
|
|
1040
|
+
progressData = JSON.parse(await fs.readFile(progressFile, 'utf-8'));
|
|
1041
|
+
} catch {
|
|
1042
|
+
|
|
1043
|
+
}
|
|
1044
|
+
const hasNumericProgress = progressData && typeof progressData.progress === 'number';
|
|
1045
|
+
const isProgressIncomplete =
|
|
1046
|
+
!!hasNumericProgress &&
|
|
1047
|
+
Number.isFinite(progressData.total) &&
|
|
1048
|
+
progressData.total > 0 &&
|
|
1049
|
+
progressData.progress < progressData.total;
|
|
1050
|
+
const fileProgressSummary = parseFileProgressSummary(progressData);
|
|
1051
|
+
|
|
1052
|
+
console.info(`${'─'.repeat(60)}`);
|
|
1053
|
+
console.info(`📁 Cache: ${dir}`);
|
|
1054
|
+
console.info(` Path: ${cacheDir}`);
|
|
1055
|
+
|
|
879
1056
|
let metaData = null;
|
|
880
1057
|
try {
|
|
881
1058
|
metaData = JSON.parse(await fs.readFile(metaFile, 'utf-8'));
|
|
@@ -934,25 +1111,50 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
934
1111
|
if (Number.isFinite(metaData.lastWorkerThreads)) {
|
|
935
1112
|
console.info(` Last worker threads: ${metaData.lastWorkerThreads}`);
|
|
936
1113
|
}
|
|
937
|
-
try {
|
|
938
|
-
const dirStats = await fs.stat(cacheDir);
|
|
939
|
-
console.info(` Cache dir last write: ${formatDateTime(dirStats.mtime)}`);
|
|
940
|
-
} catch {
|
|
941
|
-
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1114
|
+
try {
|
|
1115
|
+
const dirStats = await fs.stat(cacheDir);
|
|
1116
|
+
console.info(` Cache dir last write: ${formatDateTime(dirStats.mtime)}`);
|
|
1117
|
+
} catch {
|
|
1118
|
+
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
const progressAfterSnapshot =
|
|
1122
|
+
hasNumericProgress &&
|
|
1123
|
+
progressData.updatedAt &&
|
|
1124
|
+
metaData.lastSaveTime &&
|
|
1125
|
+
new Date(progressData.updatedAt) > new Date(metaData.lastSaveTime);
|
|
1126
|
+
const isIncrementalUpdateActive = Boolean(
|
|
1127
|
+
hasNumericProgress && (isProgressIncomplete || progressAfterSnapshot)
|
|
1128
|
+
);
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
if (metaData.filesIndexed && metaData.filesIndexed > 0) {
|
|
1132
|
+
if (isIncrementalUpdateActive) {
|
|
1133
|
+
console.info(` Cached snapshot: ✅ COMPLETE (${metaData.filesIndexed} files)`);
|
|
1134
|
+
} else {
|
|
1135
|
+
console.info(` Cached index: ✅ COMPLETE (${metaData.filesIndexed} files)`);
|
|
1136
|
+
}
|
|
1137
|
+
} else if (metaData.filesIndexed === 0) {
|
|
1138
|
+
console.info(` Cached index: ⚠️ NO FILES (check excludePatterns)`);
|
|
1139
|
+
} else {
|
|
1140
|
+
console.info(` Cached index: ⚠️ INCOMPLETE`);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
if (
|
|
1144
|
+
isIncrementalUpdateActive &&
|
|
1145
|
+
Number.isFinite(fileProgressSummary?.total) &&
|
|
1146
|
+
Number.isFinite(metaData.filesIndexed) &&
|
|
1147
|
+
fileProgressSummary.total > metaData.filesIndexed
|
|
1148
|
+
) {
|
|
1149
|
+
const delta = fileProgressSummary.total - metaData.filesIndexed;
|
|
1150
|
+
console.info(
|
|
1151
|
+
` Current run target: ${fileProgressSummary.total} files (${delta} more than cached snapshot)`
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
} catch (err) {
|
|
1155
|
+
if (err.code === 'ENOENT') {
|
|
1156
|
+
try {
|
|
1157
|
+
const stats = await fs.stat(cacheDir);
|
|
956
1158
|
const ageMs = new Date() - stats.mtime;
|
|
957
1159
|
if (ageMs < 10 * 60 * 1000) {
|
|
958
1160
|
console.info(` Status: ⏳ Initializing / Indexing in progress...`);
|
|
@@ -967,21 +1169,13 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
967
1169
|
}
|
|
968
1170
|
} else {
|
|
969
1171
|
console.info(` Status: ❌ Invalid or corrupted (${err.message})`);
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
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';
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
if (progressData && typeof progressData.progress === 'number') {
|
|
1176
|
+
const updatedAt = progressData.updatedAt
|
|
1177
|
+
? formatDateTime(progressData.updatedAt)
|
|
1178
|
+
: 'Unknown';
|
|
985
1179
|
const progressLabel = metaData
|
|
986
1180
|
? 'Incremental update (post-snapshot)'
|
|
987
1181
|
: 'Initial index progress';
|
|
@@ -1026,18 +1220,18 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
1026
1220
|
);
|
|
1027
1221
|
}
|
|
1028
1222
|
} 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 &&
|
|
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') {
|
|
1223
|
+
if (metaData) {
|
|
1224
|
+
console.info(' Summary: Cached snapshot available; no update running.');
|
|
1225
|
+
} else {
|
|
1226
|
+
console.info(' Summary: No cached snapshot yet; indexing has not started.');
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
if (metaData && isProgressIncomplete) {
|
|
1231
|
+
console.info(' Indexing state: Cached snapshot available; incremental update running.');
|
|
1232
|
+
} else if (metaData) {
|
|
1233
|
+
console.info(' Indexing state: Cached snapshot available; idle.');
|
|
1234
|
+
} else if (progressData && typeof progressData.progress === 'number') {
|
|
1041
1235
|
console.info(' Indexing state: Initial index in progress; no cached snapshot yet.');
|
|
1042
1236
|
} else {
|
|
1043
1237
|
console.info(' Indexing state: No cached snapshot; idle.');
|
|
@@ -1064,22 +1258,22 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
1064
1258
|
}
|
|
1065
1259
|
}
|
|
1066
1260
|
|
|
1067
|
-
|
|
1261
|
+
|
|
1068
1262
|
if (!cacheOnly) {
|
|
1069
|
-
|
|
1263
|
+
|
|
1070
1264
|
console.info('\n[Paths] Important locations:');
|
|
1071
1265
|
|
|
1072
|
-
|
|
1266
|
+
|
|
1073
1267
|
let npmBin = 'unknown';
|
|
1074
1268
|
try {
|
|
1075
1269
|
const { stdout } = await execPromise('npm config get prefix');
|
|
1076
1270
|
npmBin = path.join(stdout.trim(), 'bin');
|
|
1077
1271
|
} catch {
|
|
1078
|
-
|
|
1272
|
+
|
|
1079
1273
|
}
|
|
1080
1274
|
console.info(` 📦 Global npm bin: ${npmBin}`);
|
|
1081
1275
|
|
|
1082
|
-
|
|
1276
|
+
|
|
1083
1277
|
const configLocations = [
|
|
1084
1278
|
{
|
|
1085
1279
|
name: 'Antigravity',
|
|
@@ -1115,12 +1309,12 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
1115
1309
|
},
|
|
1116
1310
|
];
|
|
1117
1311
|
|
|
1118
|
-
|
|
1312
|
+
|
|
1119
1313
|
if (process.platform === 'darwin') {
|
|
1120
1314
|
configLocations[2].path = path.join(
|
|
1121
1315
|
os.homedir(),
|
|
1122
|
-
|
|
1123
|
-
|
|
1316
|
+
|
|
1317
|
+
|
|
1124
1318
|
'Library',
|
|
1125
1319
|
'Application Support',
|
|
1126
1320
|
'Claude',
|
|
@@ -1173,7 +1367,7 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
1173
1367
|
await fs.access(loc.path);
|
|
1174
1368
|
status = '(exists)';
|
|
1175
1369
|
} catch {
|
|
1176
|
-
|
|
1370
|
+
|
|
1177
1371
|
}
|
|
1178
1372
|
console.info(` - ${loc.name}: ${loc.path} ${status}`);
|
|
1179
1373
|
}
|
|
@@ -1182,7 +1376,7 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
1182
1376
|
console.info(` 💾 Cache root: ${globalCacheRoot}`);
|
|
1183
1377
|
console.info(` 📁 Current dir: ${process.cwd()}`);
|
|
1184
1378
|
console.info('');
|
|
1185
|
-
}
|
|
1379
|
+
}
|
|
1186
1380
|
} catch (error) {
|
|
1187
1381
|
console.error(`[Lifecycle] Failed to check status: ${error.message}`);
|
|
1188
1382
|
}
|