mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-09 03:17:54 +00:00
Surface Windows MCP launcher drift in doctor
Windows npm updates can leave older GitNexus MCP Node processes running from npx caches or stale package roots, which makes the next install/update look broken when native files stay locked. The doctor command now reports Windows MCP process sources so users can restart editors/agents or stop current MCP servers before retrying the update. Constraint: Windows package updates can leave node-based MCP servers holding native addon files open Rejected: Auto-stop GitNexus MCP processes from doctor | too side-effectful for a diagnostic command Confidence: medium Scope-risk: narrow Directive: Keep the Windows process scan best-effort and non-fatal; doctor must still work if PowerShell or CIM process lookup is unavailable Tested: npx vitest run test/unit/doctor-format.test.ts; npx tsc --noEmit; npm run build; npx eslint gitnexus/src/cli/doctor.ts gitnexus/test/unit/doctor-format.test.ts gitnexus/scripts/cross-platform-tests.ts; git diff --check; GitNexus detect_changes Not-tested: Full npm test passes on this Windows shell; broader unit and cross-platform suites have unrelated Windows/env failures noted in the PR
This commit is contained in:
parent
fff01189b1
commit
e61b3e3278
3 changed files with 180 additions and 1 deletions
|
|
@ -30,6 +30,7 @@ const PLATFORM_LOGIC = [
|
|||
'test/unit/setup-jsonc.test.ts',
|
||||
'test/unit/setup-codex.test.ts',
|
||||
'test/unit/setup-antigravity.test.ts',
|
||||
'test/unit/doctor-format.test.ts',
|
||||
'test/unit/resolve-invocation.test.ts',
|
||||
'test/unit/platform-capabilities.test.ts',
|
||||
'test/unit/worker-pool-windows-quarantine.test.ts',
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import { execFile } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import { promisify } from 'node:util';
|
||||
import { getRuntimeCapabilities, getRuntimeFingerprint } from '../core/platform/capabilities.js';
|
||||
import { resolveEmbeddingConfig } from '../core/embeddings/config.js';
|
||||
import { isHttpMode } from '../core/embeddings/http-client.js';
|
||||
|
|
@ -6,6 +9,8 @@ import { checkLbugNative } from '../core/lbug/native-check.js';
|
|||
import { getExtensionInstallPolicy } from '../core/lbug/extension-loader.js';
|
||||
import { t } from './i18n/index.js';
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
function isCombiningMark(codePoint: number): boolean {
|
||||
return (
|
||||
(codePoint >= 0x0300 && codePoint <= 0x036f) ||
|
||||
|
|
@ -78,6 +83,114 @@ export function localEmbeddingDoctorStatus(opts: {
|
|||
return { status: '✓ local embeddings supported', detail: null };
|
||||
}
|
||||
|
||||
export type WindowsGitNexusMcpProcess = {
|
||||
pid: number;
|
||||
commandLine: string;
|
||||
};
|
||||
|
||||
export type WindowsGitNexusMcpStatus = {
|
||||
status: string;
|
||||
detail: string | null;
|
||||
};
|
||||
|
||||
function normalizePathForMatch(value: string): string {
|
||||
return value.replace(/\\/g, '/').toLowerCase();
|
||||
}
|
||||
|
||||
function currentPackageRootFromArgv(argv1: string | undefined): string | null {
|
||||
if (!argv1) return null;
|
||||
const normalized = path.resolve(argv1);
|
||||
const parts = normalized.split(path.sep);
|
||||
const packageIndex = parts.map((part) => part.toLowerCase()).lastIndexOf('gitnexus');
|
||||
if (packageIndex < 0) return null;
|
||||
return parts.slice(0, packageIndex + 1).join(path.sep);
|
||||
}
|
||||
|
||||
function parseWindowsProcessJson(stdout: string): WindowsGitNexusMcpProcess[] {
|
||||
const trimmed = stdout.trim();
|
||||
if (!trimmed) return [];
|
||||
const parsed = JSON.parse(trimmed) as
|
||||
| { pid?: number; ProcessId?: number; commandLine?: string; CommandLine?: string }
|
||||
| Array<{ pid?: number; ProcessId?: number; commandLine?: string; CommandLine?: string }>;
|
||||
const rows = Array.isArray(parsed) ? parsed : [parsed];
|
||||
return rows
|
||||
.map((row) => ({
|
||||
pid: Number(row.pid ?? row.ProcessId),
|
||||
commandLine: String(row.commandLine ?? row.CommandLine ?? ''),
|
||||
}))
|
||||
.filter((row) => Number.isFinite(row.pid) && row.commandLine.length > 0);
|
||||
}
|
||||
|
||||
export async function listWindowsGitNexusMcpProcesses(
|
||||
opts: {
|
||||
platform?: NodeJS.Platform;
|
||||
} = {},
|
||||
): Promise<WindowsGitNexusMcpProcess[]> {
|
||||
if ((opts.platform ?? process.platform) !== 'win32') return [];
|
||||
|
||||
const script = [
|
||||
"$ErrorActionPreference = 'Stop'",
|
||||
'Get-CimInstance Win32_Process -Filter "name = \'node.exe\'"',
|
||||
" | Where-Object { $_.CommandLine -match 'gitnexus' -and $_.CommandLine -match '\\bmcp\\b' }",
|
||||
' | Select-Object @{Name="pid";Expression={$_.ProcessId}}, @{Name="commandLine";Expression={$_.CommandLine}}',
|
||||
' | ConvertTo-Json -Compress',
|
||||
].join('\n');
|
||||
|
||||
try {
|
||||
const { stdout } = await execFileAsync(
|
||||
'powershell.exe',
|
||||
['-NoProfile', '-NonInteractive', '-Command', script],
|
||||
{ timeout: 1500, windowsHide: true, maxBuffer: 1024 * 1024 },
|
||||
);
|
||||
return parseWindowsProcessJson(stdout);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function windowsGitNexusMcpDoctorStatus(opts: {
|
||||
processes: WindowsGitNexusMcpProcess[];
|
||||
currentPackageRoot?: string | null;
|
||||
}): WindowsGitNexusMcpStatus {
|
||||
if (opts.processes.length === 0) {
|
||||
return { status: 'none running', detail: null };
|
||||
}
|
||||
|
||||
const currentRoot = opts.currentPackageRoot
|
||||
? normalizePathForMatch(opts.currentPackageRoot)
|
||||
: null;
|
||||
let currentInstall = 0;
|
||||
let npxCache = 0;
|
||||
let otherInstall = 0;
|
||||
|
||||
for (const proc of opts.processes) {
|
||||
const command = normalizePathForMatch(proc.commandLine);
|
||||
if (currentRoot && command.includes(currentRoot)) {
|
||||
currentInstall += 1;
|
||||
} else if (command.includes('/_npx/') || command.includes('/npx-cli.js')) {
|
||||
npxCache += 1;
|
||||
} else {
|
||||
otherInstall += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const launchers = [
|
||||
currentInstall > 0 ? `${currentInstall} current` : null,
|
||||
npxCache > 0 ? `${npxCache} npx-cache` : null,
|
||||
otherInstall > 0 ? `${otherInstall} other` : null,
|
||||
].filter(Boolean);
|
||||
|
||||
const detail =
|
||||
npxCache > 0 || otherInstall > 0
|
||||
? 'Restart editors/agents after updating GitNexus so MCP servers reload the same package version.'
|
||||
: 'Stop running MCP servers before npm updates if cleanup reports locked native files.';
|
||||
|
||||
return {
|
||||
status: `${opts.processes.length} running (${launchers.join(', ')})`,
|
||||
detail,
|
||||
};
|
||||
}
|
||||
|
||||
export const doctorCommand = async () => {
|
||||
const fingerprint = getRuntimeFingerprint();
|
||||
const capabilities = getRuntimeCapabilities();
|
||||
|
|
@ -139,4 +252,17 @@ export const doctorCommand = async () => {
|
|||
if (support.detail) {
|
||||
process.stderr.write(`\n${support.detail.replace(/^/gm, ' ')}\n\n`);
|
||||
}
|
||||
if (process.platform === 'win32') {
|
||||
const mcpProcesses = await listWindowsGitNexusMcpProcesses();
|
||||
const mcpStatus = windowsGitNexusMcpDoctorStatus({
|
||||
processes: mcpProcesses,
|
||||
currentPackageRoot: currentPackageRootFromArgv(process.argv[1]),
|
||||
});
|
||||
console.log('');
|
||||
console.log('Windows MCP');
|
||||
console.log(` ${padDisplayEnd('Processes:', 18)}${mcpStatus.status}`);
|
||||
if (mcpStatus.detail) {
|
||||
console.log(` ${padDisplayEnd('Note:', 18)}${mcpStatus.detail}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { displayWidth, localEmbeddingDoctorStatus, padDisplayEnd } from '../../src/cli/doctor.js';
|
||||
import {
|
||||
displayWidth,
|
||||
localEmbeddingDoctorStatus,
|
||||
padDisplayEnd,
|
||||
windowsGitNexusMcpDoctorStatus,
|
||||
} from '../../src/cli/doctor.js';
|
||||
|
||||
describe('doctor output formatting', () => {
|
||||
it('keeps ASCII padding equivalent to String.padEnd', () => {
|
||||
|
|
@ -55,3 +60,50 @@ describe('doctor embedding-runtime support status', () => {
|
|||
expect(detail).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('doctor Windows GitNexus MCP process status', () => {
|
||||
it('reports no running processes without extra guidance', () => {
|
||||
const status = windowsGitNexusMcpDoctorStatus({ processes: [] });
|
||||
|
||||
expect(status).toEqual({ status: 'none running', detail: null });
|
||||
});
|
||||
|
||||
it('recognizes current-install MCP processes and warns about update locks', () => {
|
||||
const status = windowsGitNexusMcpDoctorStatus({
|
||||
currentPackageRoot: 'D:\\home\\jonkomet\\npm-global\\node_modules\\gitnexus',
|
||||
processes: [
|
||||
{
|
||||
pid: 1234,
|
||||
commandLine:
|
||||
'"C:\\Program Files\\nodejs\\node.exe" D:\\home\\jonkomet\\npm-global\\node_modules\\gitnexus\\dist\\cli\\index.js mcp',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(status.status).toBe('1 running (1 current)');
|
||||
expect(status.detail).toMatch(/Stop running MCP servers/);
|
||||
expect(status.detail).toMatch(/locked native files/);
|
||||
});
|
||||
|
||||
it('flags mixed current and npx-cache launchers after updates', () => {
|
||||
const status = windowsGitNexusMcpDoctorStatus({
|
||||
currentPackageRoot: 'D:\\home\\jonkomet\\npm-global\\node_modules\\gitnexus',
|
||||
processes: [
|
||||
{
|
||||
pid: 1234,
|
||||
commandLine:
|
||||
'"C:\\Program Files\\nodejs\\node.exe" D:\\home\\jonkomet\\npm-global\\node_modules\\gitnexus\\dist\\cli\\index.js mcp',
|
||||
},
|
||||
{
|
||||
pid: 5678,
|
||||
commandLine:
|
||||
'"node" "C:\\Users\\Jaybo\\AppData\\Local\\npm-cache\\_npx\\abc\\node_modules\\.bin\\..\\gitnexus\\dist\\cli\\index.js" mcp',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(status.status).toBe('2 running (1 current, 1 npx-cache)');
|
||||
expect(status.detail).toMatch(/Restart editors\/agents/);
|
||||
expect(status.detail).toMatch(/same package version/);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue