gitnexus 1.6.5-rc.35 → 1.6.5-rc.37

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/dist/cli/setup.js CHANGED
@@ -314,6 +314,18 @@ async function installClaudeCodeHooks(result) {
314
314
  catch {
315
315
  // Helper not found in source — skip
316
316
  }
317
+ try {
318
+ await fs.copyFile(path.join(pluginHooksPath, 'hook-db-lock-probe.cjs'), path.join(destHooksDir, 'hook-db-lock-probe.cjs'));
319
+ }
320
+ catch {
321
+ // Helper not found in source — skip
322
+ }
323
+ try {
324
+ await fs.copyFile(path.join(pluginHooksPath, 'win-rm-list-json.ps1'), path.join(destHooksDir, 'win-rm-list-json.ps1'));
325
+ }
326
+ catch {
327
+ // Helper not found in source — skip
328
+ }
317
329
  const hookPath = path.join(destHooksDir, 'gitnexus-hook.cjs').replace(/\\/g, '/');
318
330
  // Escape backslashes FIRST, then quotes (CodeQL js/incomplete-sanitization).
319
331
  // The previous shape `replace(/"/g, '\\"')` alone would let `path\with"quote`
@@ -52,6 +52,13 @@ export const resolveImportPath = (currentFile, importPath, allFiles, allFileList
52
52
  const resolved = tryResolveWithExtensions(rewritten, allFiles);
53
53
  if (resolved)
54
54
  return cache(resolved);
55
+ // ESM fallback: strip .js/.jsx/.mjs/.cjs and retry with TS equivalents
56
+ const strippedAlias = stripJsExtension(rewritten);
57
+ if (strippedAlias !== null) {
58
+ const esmResolved = tryResolveWithExtensions(strippedAlias, allFiles);
59
+ if (esmResolved)
60
+ return cache(esmResolved);
61
+ }
55
62
  // Try suffix matching as fallback
56
63
  const parts = rewritten.split('/').filter(Boolean);
57
64
  const suffixResult = suffixResolve(parts, normalizedFileList, allFileList, index);
@@ -112,9 +119,6 @@ export const resolveImportPath = (currentFile, importPath, allFiles, allFileList
112
119
  return cache(resolved);
113
120
  // TypeScript ESM: imports use .js/.jsx/.mjs/.cjs but source files are
114
121
  // .ts/.tsx/.mts/.cts. Strip the JS-family extension and re-resolve.
115
- // NOTE: This fallback only applies to relative imports. Path alias imports
116
- // (e.g. @/utils.js via tsconfig paths) do not yet strip .js extensions —
117
- // that is a known limitation tracked for follow-up.
118
122
  if (language === SupportedLanguages.TypeScript || language === SupportedLanguages.JavaScript) {
119
123
  const stripped = stripJsExtension(basePath);
120
124
  if (stripped !== null) {
@@ -10,6 +10,7 @@ import { realpathSync } from 'fs';
10
10
  import path from 'path';
11
11
  import os from 'os';
12
12
  import { getInferredRepoName, resolveRepoIdentityRoot } from './git.js';
13
+ import { logger } from '../core/logger.js';
13
14
  /**
14
15
  * Normalise a repo path for registry comparison across platforms
15
16
  * (#664 review feedback from @evander-wang).
@@ -200,13 +201,42 @@ export const findRepo = async (startPath) => {
200
201
  }
201
202
  return null;
202
203
  };
204
+ function isReadOnlyFilesystemError(err) {
205
+ const code = err?.code;
206
+ return code === 'EROFS' || code === 'EACCES' || code === 'EPERM';
207
+ }
203
208
  /**
204
209
  * Keep generated index files ignored without modifying the user's root .gitignore.
205
210
  */
206
211
  export const ensureGitNexusIgnored = async (repoPath) => {
207
212
  const gitignorePath = path.join(getStoragePath(repoPath), '.gitignore');
208
- await fs.mkdir(path.dirname(gitignorePath), { recursive: true });
209
- await fs.writeFile(gitignorePath, '*\n', 'utf-8');
213
+ const desired = '*\n';
214
+ // Idempotent fast path: skip the write entirely when the file already has
215
+ // the expected content. Lets this run cleanly on read-only mounts (e.g.
216
+ // the documented Docker workflow with WORKSPACE_DIR bound :ro) when an
217
+ // earlier `analyze` already created the file. See issue #1549.
218
+ try {
219
+ if ((await fs.readFile(gitignorePath, 'utf-8')) === desired) {
220
+ await ensureGitInfoExclude(repoPath);
221
+ return;
222
+ }
223
+ }
224
+ catch (err) {
225
+ if (err?.code !== 'ENOENT')
226
+ throw err;
227
+ }
228
+ try {
229
+ await fs.mkdir(path.dirname(gitignorePath), { recursive: true });
230
+ await fs.writeFile(gitignorePath, desired, 'utf-8');
231
+ }
232
+ catch (err) {
233
+ if (isReadOnlyFilesystemError(err)) {
234
+ logger.warn({ path: gitignorePath, code: err.code }, 'GitNexus storage filesystem is not writable; skipping .gitnexus/.gitignore. Generated files may appear as untracked in this repo locally.');
235
+ }
236
+ else {
237
+ throw err;
238
+ }
239
+ }
210
240
  await ensureGitInfoExclude(repoPath);
211
241
  };
212
242
  const ensureGitInfoExclude = async (repoPath) => {
@@ -220,7 +250,6 @@ const ensureGitInfoExclude = async (repoPath) => {
220
250
  catch {
221
251
  return;
222
252
  }
223
- await fs.mkdir(path.dirname(excludePath), { recursive: true });
224
253
  let content = '';
225
254
  try {
226
255
  content = await fs.readFile(excludePath, 'utf-8');
@@ -236,7 +265,18 @@ const ensureGitInfoExclude = async (repoPath) => {
236
265
  if (excludes.includes(GITNEXUS_DIR) || excludes.includes(GITNEXUS_EXCLUDE_ENTRY))
237
266
  return;
238
267
  const separator = content.length === 0 || content.endsWith('\n') ? '' : '\n';
239
- await fs.writeFile(excludePath, `${content}${separator}${GITNEXUS_EXCLUDE_ENTRY}\n`, 'utf-8');
268
+ try {
269
+ await fs.mkdir(path.dirname(excludePath), { recursive: true });
270
+ await fs.writeFile(excludePath, `${content}${separator}${GITNEXUS_EXCLUDE_ENTRY}\n`, 'utf-8');
271
+ }
272
+ catch (err) {
273
+ if (isReadOnlyFilesystemError(err)) {
274
+ logger.warn({ path: excludePath, code: err.code }, 'GitNexus storage filesystem is not writable; skipping .git/info/exclude update. .gitnexus/ may appear as untracked in `git status` locally.');
275
+ }
276
+ else {
277
+ throw err;
278
+ }
279
+ }
240
280
  };
241
281
  // ─── Global Registry (~/.gitnexus/registry.json) ───────────────────────
242
282
  /**
@@ -15,6 +15,7 @@ const fs = require('fs');
15
15
  const path = require('path');
16
16
  const { spawnSync } = require('child_process');
17
17
  const { acquireHookSlot } = require('./hook-lock.cjs');
18
+ const { hasGitNexusDbLockedByGitNexusServer } = require('./hook-db-lock-probe.cjs');
18
19
 
19
20
  /**
20
21
  * Read JSON input from stdin synchronously.
@@ -103,6 +104,28 @@ function findGitNexusDir(startDir) {
103
104
  return null;
104
105
  }
105
106
 
107
+ function hasGitNexusServerOwner(gitNexusDir) {
108
+ return hasGitNexusDbLockedByGitNexusServer(path.join(gitNexusDir, 'lbug'), process.pid);
109
+ }
110
+
111
+ function extractAugmentContext(stderr) {
112
+ const output = (stderr || '').trim();
113
+ const marker = output.indexOf('[GitNexus]');
114
+ const debug = process.env.GITNEXUS_DEBUG === '1' || process.env.GITNEXUS_DEBUG === 'true';
115
+ if (debug && output.length > 0) {
116
+ // Emit the FULL discarded prefix (everything before the marker, or all of
117
+ // it when no marker is present) so suppressed diagnostics — KuzuDB lock
118
+ // warnings, parser errors, etc. — remain recoverable on the hook's own
119
+ // stderr. The untruncated payload lets operators see exactly what was
120
+ // filtered out instead of a 180-char JSON-quoted preview.
121
+ const discarded = marker === -1 ? output : output.slice(0, marker).trim();
122
+ if (discarded.length > 0) {
123
+ process.stderr.write(`[GitNexus hook] augment stderr discarded prefix:\n${discarded}\n`);
124
+ }
125
+ }
126
+ return marker === -1 ? '' : output.slice(marker).trim();
127
+ }
128
+
106
129
  /**
107
130
  * Extract search pattern from tool input.
108
131
  */
@@ -168,6 +191,10 @@ function extractPattern(toolName, toolInput) {
168
191
  * 3. Fall back to npx (returns empty string)
169
192
  */
170
193
  function resolveCliPath() {
194
+ const fromEnv = process.env.GITNEXUS_HOOK_CLI_PATH;
195
+ if (fromEnv !== undefined && String(fromEnv).trim() && fs.existsSync(String(fromEnv))) {
196
+ return String(fromEnv);
197
+ }
171
198
  let cliPath = path.resolve(__dirname, '..', '..', 'dist', 'cli', 'index.js');
172
199
  if (!fs.existsSync(cliPath)) {
173
200
  try {
@@ -218,6 +245,10 @@ function handlePreToolUse(input) {
218
245
 
219
246
  const pattern = extractPattern(toolName, toolInput);
220
247
  if (!pattern || pattern.length < 3) return;
248
+ if (hasGitNexusServerOwner(gitNexusDir)) {
249
+ process.stderr.write('[GitNexus] augment skipped: MCP server owns DB\n');
250
+ return;
251
+ }
221
252
 
222
253
  const release = acquireHookSlot(gitNexusDir);
223
254
  if (!release) return;
@@ -227,7 +258,7 @@ function handlePreToolUse(input) {
227
258
  try {
228
259
  const child = runGitNexusCli(cliPath, ['augment', '--', pattern], cwd, 7000);
229
260
  if (!child.error && child.status === 0) {
230
- result = child.stderr || '';
261
+ result = extractAugmentContext(child.stderr || '');
231
262
  }
232
263
  } catch {
233
264
  /* graceful failure */
@@ -235,8 +266,8 @@ function handlePreToolUse(input) {
235
266
  release();
236
267
  }
237
268
 
238
- if (result && result.trim()) {
239
- sendHookResponse('PreToolUse', result.trim());
269
+ if (result) {
270
+ sendHookResponse('PreToolUse', result);
240
271
  }
241
272
  }
242
273
 
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Cross-platform best-effort probe: does another process hold dbPath open
3
+ * with a command line that looks like a GitNexus MCP/serve server?
4
+ *
5
+ * Backends (no user-installed Sysinternals):
6
+ * - Linux: scan procfs under /proc (per-PID fd entries) via stat(2) (dev+inode); works without lsof;
7
+ * optional lsof fallback when proc scan finds nothing.
8
+ * - macOS / *BSD / etc.: trusted lsof + ps (absolute paths first).
9
+ * - Windows: Restart Manager (rstrtmgr) via bundled PowerShell script +
10
+ * Win32_Process for command lines; trusted powershell.exe under %SystemRoot%.
11
+ *
12
+ * Fail-open on most errors; fail-closed only on lsof ETIMEDOUT (Unix) or
13
+ * PowerShell ETIMEDOUT (Windows), matching the hook contract.
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const { spawnSync } = require('child_process');
19
+
20
+ function isGitNexusServerCommand(command) {
21
+ const hasServerMode = /(?:^|\s)(mcp|serve)(?:\s|$)/.test(command);
22
+ const hasGitNexus =
23
+ /(?:^|[/\\\s])gitnexus(?:\.cmd)?(?:\s|$)/.test(command) ||
24
+ /node_modules[/\\]gitnexus[/\\]/.test(command);
25
+ return hasServerMode && hasGitNexus;
26
+ }
27
+
28
+ function resolveHookBinary(tool) {
29
+ const envKey = tool === 'lsof' ? 'GITNEXUS_HOOK_LSOF_PATH' : 'GITNEXUS_HOOK_PS_PATH';
30
+ const fromEnv = process.env[envKey];
31
+ if (fromEnv && String(fromEnv).trim() && fs.existsSync(String(fromEnv))) {
32
+ return String(fromEnv);
33
+ }
34
+ const candidates =
35
+ tool === 'lsof'
36
+ ? ['/usr/bin/lsof', '/usr/sbin/lsof', '/sbin/lsof', tool]
37
+ : ['/bin/ps', '/usr/bin/ps', tool];
38
+ for (const candidate of candidates) {
39
+ if (candidate === tool) return tool;
40
+ try {
41
+ if (fs.existsSync(candidate)) return candidate;
42
+ } catch {
43
+ /* ignore */
44
+ }
45
+ }
46
+ return tool;
47
+ }
48
+
49
+ function resolveWindowsPowerShellPath() {
50
+ const fromEnv = process.env.GITNEXUS_HOOK_POWERSHELL_PATH;
51
+ if (fromEnv && String(fromEnv).trim() && fs.existsSync(String(fromEnv).trim())) {
52
+ return String(fromEnv).trim();
53
+ }
54
+ const root = process.env.SystemRoot || 'C:\\Windows';
55
+ const ps = path.join(root, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
56
+ if (fs.existsSync(ps)) return ps;
57
+ const psWow = path.join(root, 'SysWOW64', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
58
+ if (fs.existsSync(psWow)) return psWow;
59
+ return 'powershell.exe';
60
+ }
61
+
62
+ // Sentinel:
63
+ // undefined = not loaded yet (try the read)
64
+ // string = encoded PowerShell command (successful load)
65
+ // null = load attempted and failed (do not retry; warning already emitted)
66
+ let windowsRmListPsEncodedCommandCache;
67
+ let windowsRmListPsLoadFailureWarned = false;
68
+ function getWindowsRmListEncodedCommand() {
69
+ if (windowsRmListPsEncodedCommandCache !== undefined) {
70
+ return windowsRmListPsEncodedCommandCache;
71
+ }
72
+ try {
73
+ const ps1Path = path.join(__dirname, 'win-rm-list-json.ps1');
74
+ const src = fs
75
+ .readFileSync(ps1Path, 'utf8')
76
+ .replace(/^\uFEFF/, '')
77
+ .replace(/\r\n/g, '\n');
78
+ windowsRmListPsEncodedCommandCache = Buffer.from(src, 'utf16le').toString('base64');
79
+ } catch (err) {
80
+ windowsRmListPsEncodedCommandCache = null;
81
+ if (
82
+ !windowsRmListPsLoadFailureWarned &&
83
+ (process.env.GITNEXUS_DEBUG === '1' || process.env.GITNEXUS_DEBUG === 'true')
84
+ ) {
85
+ windowsRmListPsLoadFailureWarned = true;
86
+ const msg = err && err.message ? String(err.message).slice(0, 200) : 'unknown';
87
+ process.stderr.write(`[GitNexus hook] win-rm-list-json.ps1 load failed: ${msg}\n`);
88
+ }
89
+ }
90
+ return windowsRmListPsEncodedCommandCache;
91
+ }
92
+
93
+ function hasGitNexusServerOwnerWindows(dbPathAbs, myPid) {
94
+ const encoded = getWindowsRmListEncodedCommand();
95
+ if (!encoded) return false;
96
+ const psExe = resolveWindowsPowerShellPath();
97
+ const r = spawnSync(
98
+ psExe,
99
+ [
100
+ '-NoProfile',
101
+ '-NonInteractive',
102
+ '-ExecutionPolicy',
103
+ 'Bypass',
104
+ '-STA',
105
+ '-EncodedCommand',
106
+ encoded,
107
+ ],
108
+ {
109
+ encoding: 'utf-8',
110
+ timeout: 6000,
111
+ stdio: ['ignore', 'pipe', 'ignore'],
112
+ env: { ...process.env, GITNEXUS_HOOK_RM_TARGET: dbPathAbs },
113
+ },
114
+ );
115
+ // ETIMEDOUT means the PowerShell probe didn't return in time; treat as 'unresponsive process holds DB' → fail-closed (skip augment).
116
+ if (r.error) return r.error.code === 'ETIMEDOUT';
117
+ if (r.status !== 0) return false;
118
+ let rows;
119
+ try {
120
+ rows = JSON.parse(String(r.stdout || '').trim() || '[]');
121
+ } catch {
122
+ return false;
123
+ }
124
+ if (!Array.isArray(rows)) return false;
125
+ for (const row of rows) {
126
+ const procId = Number(row.pid);
127
+ const cmd = String(row.cmd || '');
128
+ if (!Number.isFinite(procId) || procId === myPid) continue;
129
+ if (isGitNexusServerCommand(cmd)) return true;
130
+ }
131
+ return false;
132
+ }
133
+
134
+ function readLinuxCmdline(pidStr) {
135
+ try {
136
+ return fs.readFileSync(`/proc/${pidStr}/cmdline`, 'utf8').replace(/\0+/g, ' ').trim();
137
+ } catch {
138
+ return '';
139
+ }
140
+ }
141
+
142
+ function linuxProcScanFindGitNexusServer(dbPathAbs, myPid) {
143
+ const raw = process.env.GITNEXUS_HOOK_LINUX_PROC_BUDGET_MS;
144
+ const budget = Number(raw && String(raw).trim()) ? Number.parseInt(String(raw), 10) : 1200;
145
+ const start = Date.now();
146
+ let targetStat;
147
+ try {
148
+ targetStat = fs.statSync(dbPathAbs);
149
+ } catch {
150
+ return false;
151
+ }
152
+ let procEntries;
153
+ try {
154
+ procEntries = fs.readdirSync('/proc', { withFileTypes: true });
155
+ } catch {
156
+ return false;
157
+ }
158
+ for (const ent of procEntries) {
159
+ if (Date.now() - start > budget) return false;
160
+ if (!ent.isDirectory() || !/^\d+$/.test(ent.name)) continue;
161
+ const pid = Number.parseInt(ent.name, 10);
162
+ if (!Number.isFinite(pid) || pid === myPid) continue;
163
+ const fdDir = path.join('/proc', ent.name, 'fd');
164
+ let fds;
165
+ try {
166
+ fds = fs.readdirSync(fdDir);
167
+ } catch {
168
+ continue;
169
+ }
170
+ let holds = false;
171
+ for (const fd of fds) {
172
+ if (Date.now() - start > budget) return false;
173
+ try {
174
+ const st = fs.statSync(path.join(fdDir, fd));
175
+ if (st.dev === targetStat.dev && st.ino === targetStat.ino) {
176
+ holds = true;
177
+ break;
178
+ }
179
+ } catch {
180
+ /* ignore */
181
+ }
182
+ }
183
+ if (!holds) continue;
184
+ if (isGitNexusServerCommand(readLinuxCmdline(ent.name))) return true;
185
+ }
186
+ return false;
187
+ }
188
+
189
+ function unixLsofPsFindGitNexusServer(dbPathAbs, myPid) {
190
+ const lsofPath = resolveHookBinary('lsof');
191
+ const lsof = spawnSync(lsofPath, ['-nP', '-t', '--', dbPathAbs], {
192
+ encoding: 'utf-8',
193
+ timeout: 1000,
194
+ stdio: ['ignore', 'pipe', 'ignore'],
195
+ });
196
+ if (lsof.error) return lsof.error.code === 'ETIMEDOUT';
197
+
198
+ const pids = (lsof.stdout || '').split(/\s+/).filter(Boolean);
199
+ const psPath = resolveHookBinary('ps');
200
+ for (const pid of pids) {
201
+ if (Number(pid) === myPid) continue;
202
+ const ps = spawnSync(psPath, ['-p', pid, '-o', 'command='], {
203
+ encoding: 'utf-8',
204
+ timeout: 500,
205
+ stdio: ['ignore', 'pipe', 'ignore'],
206
+ });
207
+ if (ps.error) {
208
+ if (ps.error.code === 'ETIMEDOUT') return true;
209
+ continue;
210
+ }
211
+ if (isGitNexusServerCommand(ps.stdout || '')) return true;
212
+ }
213
+ return false;
214
+ }
215
+
216
+ /**
217
+ * @param {string} dbPath Absolute or relative path to the DB file (e.g. .../lbug).
218
+ * @param {number} myPid Current process PID (hook runner), excluded from matches.
219
+ */
220
+ function hasGitNexusDbLockedByGitNexusServer(dbPath, myPid) {
221
+ if (!fs.existsSync(dbPath)) return false;
222
+ const dbPathAbs = path.resolve(dbPath);
223
+
224
+ if (process.platform === 'win32') {
225
+ return hasGitNexusServerOwnerWindows(dbPathAbs, myPid);
226
+ }
227
+
228
+ if (process.platform === 'linux') {
229
+ if (linuxProcScanFindGitNexusServer(dbPathAbs, myPid)) return true;
230
+ return unixLsofPsFindGitNexusServer(dbPathAbs, myPid);
231
+ }
232
+
233
+ return unixLsofPsFindGitNexusServer(dbPathAbs, myPid);
234
+ }
235
+
236
+ module.exports = {
237
+ hasGitNexusDbLockedByGitNexusServer,
238
+ };
@@ -0,0 +1,76 @@
1
+ $ErrorActionPreference = 'Stop'
2
+ $target = $env:GITNEXUS_HOOK_RM_TARGET
3
+ if ([string]::IsNullOrWhiteSpace($target)) { Write-Output '[]'; exit 0 }
4
+ $target = (Resolve-Path -LiteralPath $target).ProviderPath
5
+
6
+ if (-not ([Management.Automation.PSTypeName]'GitNexusHookRm.Native').Type) {
7
+ Add-Type @'
8
+ using System;
9
+ using System.Runtime.InteropServices;
10
+ namespace GitNexusHookRm {
11
+ public static class Native {
12
+ public const int ErrorMoreData = 234;
13
+ [StructLayout(LayoutKind.Sequential, Pack = 4)]
14
+ public struct RM_UNIQUE_PROCESS {
15
+ public int dwProcessId;
16
+ public long ProcessStartTime;
17
+ }
18
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
19
+ public struct RM_PROCESS_INFO {
20
+ public RM_UNIQUE_PROCESS Process;
21
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
22
+ public string strAppName;
23
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
24
+ public string strServiceShortName;
25
+ public uint ApplicationType;
26
+ public uint AppStatus;
27
+ public uint TSSessionId;
28
+ public uint bRestartable;
29
+ }
30
+ [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
31
+ public static extern int RmStartSession(out uint pSessionHandle, uint dwSessionFlags, string strSessionKey);
32
+ [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
33
+ public static extern int RmRegisterResources(uint pSessionHandle, uint nFiles, string[] rgsFileNames, uint nApplications, IntPtr rgApplications, uint nServices, string[] rgsServiceNames);
34
+ [DllImport("rstrtmgr.dll")]
35
+ public static extern int RmGetList(uint dwSessionHandle, out uint pnProcInfoNeeded, ref uint pnProcInfo, [In, Out] RM_PROCESS_INFO[] rgAffectedApps, ref uint lpdwRebootReasons);
36
+ [DllImport("rstrtmgr.dll")]
37
+ public static extern int RmEndSession(uint pSessionHandle);
38
+ }
39
+ }
40
+ '@
41
+ }
42
+
43
+ $h = [uint32]0
44
+ $key = [guid]::NewGuid().ToString('N')
45
+ $rmErr = [GitNexusHookRm.Native]::RmStartSession([ref]$h, 0, $key)
46
+ if ($rmErr -ne 0) { Write-Output '[]'; exit 0 }
47
+ $files = @($target)
48
+ $err = [GitNexusHookRm.Native]::RmRegisterResources($h, 1, $files, 0, [IntPtr]::Zero, 0, $null)
49
+ if ($err -ne 0) {
50
+ [void][GitNexusHookRm.Native]::RmEndSession($h)
51
+ Write-Output '[]'
52
+ exit 0
53
+ }
54
+ $need = [uint32]0
55
+ $n = [uint32]0
56
+ $reboot = [uint32]0
57
+ $err = [GitNexusHookRm.Native]::RmGetList($h, [ref]$need, [ref]$n, $null, [ref]$reboot)
58
+ if ($err -ne [GitNexusHookRm.Native]::ErrorMoreData) {
59
+ [void][GitNexusHookRm.Native]::RmEndSession($h)
60
+ Write-Output '[]'
61
+ exit 0
62
+ }
63
+ $n = $need
64
+ $buf = New-Object GitNexusHookRm.Native+RM_PROCESS_INFO[] ([int]$n)
65
+ $err = [GitNexusHookRm.Native]::RmGetList($h, [ref]$need, [ref]$n, $buf, [ref]$reboot)
66
+ [void][GitNexusHookRm.Native]::RmEndSession($h)
67
+ if ($err -ne 0) { Write-Output '[]'; exit 0 }
68
+
69
+ $out = @()
70
+ for ($i = 0; $i -lt [int]$n; $i++) {
71
+ $procId = $buf[$i].Process.dwProcessId
72
+ $p = Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$procId" -ErrorAction SilentlyContinue
73
+ $cmd = if ($p) { $p.CommandLine } else { '' }
74
+ $out += [PSCustomObject]@{ pid = [int]$procId; cmd = $cmd }
75
+ }
76
+ ConvertTo-Json -InputObject @($out) -Compress
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.5-rc.35",
3
+ "version": "1.6.5-rc.37",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",