gitnexus 1.6.6-rc.49 → 1.6.6-rc.50

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.
@@ -236,6 +236,7 @@ const runRespawnedAnalyze = (args, env) => new Promise((resolve) => {
236
236
  };
237
237
  const child = spawn(process.execPath, [...args], {
238
238
  stdio: ['inherit', 'pipe', 'pipe'],
239
+ windowsHide: true,
239
240
  env,
240
241
  });
241
242
  child.stdout?.on('data', (chunk) => {
package/dist/cli/setup.js CHANGED
@@ -42,6 +42,7 @@ function resolveGitnexusBin() {
42
42
  encoding: 'utf-8',
43
43
  timeout: 5000,
44
44
  stdio: ['ignore', 'pipe', 'ignore'],
45
+ windowsHide: true,
45
46
  });
46
47
  const lines = output
47
48
  .split('\n')
@@ -454,6 +455,7 @@ async function setupCodex(result) {
454
455
  const entry = getMcpEntry();
455
456
  await execFileAsync('codex', ['mcp', 'add', 'gitnexus', '--', entry.command, ...entry.args], {
456
457
  shell: process.platform === 'win32',
458
+ windowsHide: true,
457
459
  });
458
460
  result.configured.push('Codex');
459
461
  return;
package/dist/cli/wiki.js CHANGED
@@ -442,7 +442,7 @@ const wikiCommandImpl = async (inputPath, options) => {
442
442
  console.log(`\n Opening ${treeFile} in ${editor}...`);
443
443
  console.log(' Save and close the editor when done.\n');
444
444
  try {
445
- execFileSync(editor, [treeFile], { stdio: 'inherit' });
445
+ execFileSync(editor, [treeFile], { stdio: 'inherit', windowsHide: true });
446
446
  }
447
447
  catch {
448
448
  console.log(` Could not open editor. Please edit manually:\n ${treeFile}\n`);
@@ -556,7 +556,7 @@ const wikiCommandImpl = async (inputPath, options) => {
556
556
  // ─── Gist Publishing ───────────────────────────────────────────────────
557
557
  function hasGhCLI() {
558
558
  try {
559
- execSync('gh --version', { stdio: 'ignore' });
559
+ execSync('gh --version', { stdio: 'ignore', windowsHide: true });
560
560
  return true;
561
561
  }
562
562
  catch {
@@ -595,7 +595,7 @@ function isGistUrl(line) {
595
595
  }
596
596
  function publishGist(htmlPath) {
597
597
  try {
598
- const output = execFileSync('gh', ['gist', 'create', htmlPath, '--desc', 'Repository Wiki — generated by GitNexus', '--public'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
598
+ const output = execFileSync('gh', ['gist', 'create', htmlPath, '--desc', 'Repository Wiki — generated by GitNexus', '--public'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true }).trim();
599
599
  // `gh gist create` prints the gist URL as a line in the output. Find the
600
600
  // first parseable Gist URL — if no line is a valid Gist URL, fail closed
601
601
  // (do NOT fall back to lines[last]: a non-Gist last line would propagate
@@ -68,7 +68,11 @@ function isCudaAvailable() {
68
68
  // Primary: query the dynamic linker cache — covers all architectures,
69
69
  // distro layouts, and custom install paths registered with ldconfig
70
70
  try {
71
- const out = execFileSync('ldconfig', ['-p'], { timeout: 3000, encoding: 'utf-8' });
71
+ const out = execFileSync('ldconfig', ['-p'], {
72
+ timeout: 3000,
73
+ encoding: 'utf-8',
74
+ windowsHide: true,
75
+ });
72
76
  if (out.includes('libcublasLt.so.12'))
73
77
  return true;
74
78
  }
@@ -17,6 +17,7 @@ export function checkStaleness(repoPath, lastCommit) {
17
17
  cwd: repoPath,
18
18
  encoding: 'utf-8',
19
19
  stdio: ['pipe', 'pipe', 'pipe'],
20
+ windowsHide: true,
20
21
  }).trim();
21
22
  const commitsBehind = parseInt(result, 10) || 0;
22
23
  if (commitsBehind > 0) {
@@ -44,6 +45,7 @@ export async function checkStalenessAsync(repoPath, lastCommit) {
44
45
  const { stdout } = await execFileAsync('git', ['rev-list', '--count', `${lastCommit}..HEAD`], {
45
46
  cwd: repoPath,
46
47
  encoding: 'utf-8',
48
+ windowsHide: true,
47
49
  });
48
50
  const commitsBehind = parseInt(stdout.trim(), 10) || 0;
49
51
  if (commitsBehind > 0) {
@@ -73,6 +75,7 @@ function commitsAheadOfIndexed(siblingPath, indexedCommit) {
73
75
  cwd: siblingPath,
74
76
  encoding: 'utf-8',
75
77
  stdio: ['pipe', 'pipe', 'pipe'],
78
+ windowsHide: true,
76
79
  }).trim();
77
80
  return parseInt(result, 10) || 0;
78
81
  }
@@ -418,6 +418,7 @@ const reopenWritableAfterMissingShadow = async (dbPath, err) => {
418
418
  return await openLbugConnection(lbug, dbPath);
419
419
  };
420
420
  const ensureReadOnlyConnectionUsable = async (dbPath, handle) => {
421
+ let shadowReplayErr;
421
422
  try {
422
423
  await queryAndDrain(handle.conn, READ_ONLY_SHADOW_REPLAY_PROBE);
423
424
  return handle;
@@ -431,9 +432,22 @@ const ensureReadOnlyConnectionUsable = async (dbPath, handle) => {
431
432
  await closeLbugConnection(handle);
432
433
  throw err;
433
434
  }
435
+ shadowReplayErr = err;
434
436
  }
435
437
  await closeLbugConnection(handle);
436
- const writable = await openLbugConnection(lbug, dbPath);
438
+ let writable;
439
+ try {
440
+ writable = await openLbugConnection(lbug, dbPath);
441
+ }
442
+ catch (openErr) {
443
+ const code = extractErrnoCode(openErr);
444
+ if (code === 'EROFS' || code === 'EACCES' || code === 'EPERM') {
445
+ throw new Error(shadowSidecarRecoveryMessage(dbPath, shadowReplayErr) +
446
+ '\n The workspace appears to be read-only — mount it read-write to perform shadow replay recovery,' +
447
+ ' or re-run `gitnexus analyze` on a writable filesystem to rebuild the index.');
448
+ }
449
+ throw openErr;
450
+ }
437
451
  let missingShadowError;
438
452
  try {
439
453
  await queryAndDrain(writable.conn, READ_ONLY_SHADOW_REPLAY_PROBE);
@@ -573,89 +587,107 @@ const doInitLbug = async (dbPath, readOnly = false) => {
573
587
  vectorExtensionLoaded = false;
574
588
  ensuredFTSIndexes.clear();
575
589
  }
576
- // LadybugDB stores the database as a single file (not a directory).
577
- // If the path already exists, it must be a valid LadybugDB database file.
578
- // Remove stale empty directories or files from older versions.
579
- try {
580
- const stat = await fs.lstat(dbPath);
581
- if (stat.isSymbolicLink()) {
582
- // Never follow symlinks — just remove the link itself
583
- await fs.unlink(dbPath);
584
- }
585
- else if (stat.isDirectory()) {
586
- // Verify path is within expected storage directory before deleting
587
- const realPath = await fs.realpath(dbPath);
588
- const parentDir = path.dirname(dbPath);
589
- const realParent = await fs.realpath(parentDir);
590
- if (!realPath.startsWith(realParent + path.sep) && realPath !== realParent) {
591
- throw new Error(`Refusing to delete ${dbPath}: resolved path ${realPath} is outside storage directory`);
592
- }
593
- // Old-style directory database or empty leftover - remove it
594
- await fs.rm(dbPath, { recursive: true, force: true });
595
- }
596
- // If it's a file, assume it's an existing LadybugDB database - LadybugDB will open it
597
- }
598
- catch (err) {
599
- if (!isMissingFileError(err)) {
600
- throw err;
601
- }
602
- // Path doesn't exist, which is what LadybugDB wants for a new database
603
- }
604
590
  // ---------------------------------------------------------------------------
605
- // Cross-process critical section: acquire init lock, clean orphan sidecars,
606
- // and open the database. The lock prevents a TOCTOU race where another
607
- // process could create a fresh DB between our access() check and the
608
- // unlink() of stale sidecars.
591
+ // Read-only fast path: skip all filesystem mutations (path cleanup, init
592
+ // lock, orphan sidecar removal, mkdir) so the open succeeds on read-only
593
+ // filesystems such as Docker `:ro` bind mounts. The init lock exists to
594
+ // prevent a TOCTOU race during DB *creation* — read-only opens never
595
+ // create databases and don't need the lock.
609
596
  // ---------------------------------------------------------------------------
610
- const releaseInitLock = await acquireInitLock(dbPath);
611
- try {
612
- // Crash-recovery cleanup: if the main DB file is missing, stale sidecars
613
- // from an interrupted run can block fresh opens indefinitely.
597
+ if (readOnly) {
598
+ await preflightLbugSidecars(dbPath, {
599
+ mode: 'read-only',
600
+ logger,
601
+ allowQuarantine: false,
602
+ });
603
+ const opened = await openLbugConnection(lbug, dbPath, { readOnly: true });
604
+ const usable = await ensureReadOnlyConnectionUsable(dbPath, opened);
605
+ db = usable.db;
606
+ conn = usable.conn;
607
+ currentDbReadOnly = true;
608
+ }
609
+ else {
610
+ // LadybugDB stores the database as a single file (not a directory).
611
+ // If the path already exists, it must be a valid LadybugDB database file.
612
+ // Remove stale empty directories or files from older versions.
614
613
  try {
615
- await fs.access(dbPath);
614
+ const stat = await fs.lstat(dbPath);
615
+ if (stat.isSymbolicLink()) {
616
+ // Never follow symlinks — just remove the link itself
617
+ await fs.unlink(dbPath);
618
+ }
619
+ else if (stat.isDirectory()) {
620
+ // Verify path is within expected storage directory before deleting
621
+ const realPath = await fs.realpath(dbPath);
622
+ const parentDir = path.dirname(dbPath);
623
+ const realParent = await fs.realpath(parentDir);
624
+ if (!realPath.startsWith(realParent + path.sep) && realPath !== realParent) {
625
+ throw new Error(`Refusing to delete ${dbPath}: resolved path ${realPath} is outside storage directory`);
626
+ }
627
+ // Old-style directory database or empty leftover - remove it
628
+ await fs.rm(dbPath, { recursive: true, force: true });
629
+ }
630
+ // If it's a file, assume it's an existing LadybugDB database - LadybugDB will open it
616
631
  }
617
632
  catch (err) {
618
- if (isMissingFileError(err)) {
619
- // `.shadow` is documented by LadybugDB checkpointing and `.wal.checkpoint`
620
- // was observed in the #1618 crash loop that motivated this recovery path.
621
- const orphanSidecars = [`${dbPath}.shadow`, `${dbPath}.wal.checkpoint`];
622
- for (const sidecar of orphanSidecars) {
623
- try {
624
- await fs.unlink(sidecar);
625
- logger.warn(`GitNexus: removed orphan sidecar ${path.basename(sidecar)} (no main DB file present)`);
626
- }
627
- catch (err) {
628
- if (isMissingFileError(err)) {
629
- continue;
633
+ if (!isMissingFileError(err)) {
634
+ throw err;
635
+ }
636
+ // Path doesn't exist, which is what LadybugDB wants for a new database
637
+ }
638
+ // -------------------------------------------------------------------------
639
+ // Cross-process critical section: acquire init lock, clean orphan sidecars,
640
+ // and open the database. The lock prevents a TOCTOU race where another
641
+ // process could create a fresh DB between our access() check and the
642
+ // unlink() of stale sidecars.
643
+ // -------------------------------------------------------------------------
644
+ const releaseInitLock = await acquireInitLock(dbPath);
645
+ try {
646
+ // Crash-recovery cleanup: if the main DB file is missing, stale sidecars
647
+ // from an interrupted run can block fresh opens indefinitely.
648
+ try {
649
+ await fs.access(dbPath);
650
+ }
651
+ catch (err) {
652
+ if (isMissingFileError(err)) {
653
+ // `.shadow` is documented by LadybugDB checkpointing and `.wal.checkpoint`
654
+ // was observed in the #1618 crash loop that motivated this recovery path.
655
+ const orphanSidecars = [`${dbPath}.shadow`, `${dbPath}.wal.checkpoint`];
656
+ for (const sidecar of orphanSidecars) {
657
+ try {
658
+ await fs.unlink(sidecar);
659
+ logger.warn(`GitNexus: removed orphan sidecar ${path.basename(sidecar)} (no main DB file present)`);
660
+ }
661
+ catch (err) {
662
+ if (isMissingFileError(err)) {
663
+ continue;
664
+ }
665
+ const code = extractErrnoCode(err);
666
+ logger.warn(`GitNexus: failed to remove orphan sidecar ${path.basename(sidecar)} (${code ?? 'UNKNOWN'}) while main DB file is missing; LadybugDB open may still fail: ${summarizeError(err)}`);
630
667
  }
631
- const code = extractErrnoCode(err);
632
- logger.warn(`GitNexus: failed to remove orphan sidecar ${path.basename(sidecar)} (${code ?? 'UNKNOWN'}) while main DB file is missing; LadybugDB open may still fail: ${summarizeError(err)}`);
633
668
  }
634
669
  }
670
+ else {
671
+ const code = extractErrnoCode(err);
672
+ logger.warn(`GitNexus: unable to verify main DB file before orphan sidecar cleanup (${code ?? 'UNKNOWN'}); skipping cleanup: ${summarizeError(err)}`);
673
+ }
635
674
  }
636
- else {
637
- const code = extractErrnoCode(err);
638
- logger.warn(`GitNexus: unable to verify main DB file before orphan sidecar cleanup (${code ?? 'UNKNOWN'}); skipping cleanup: ${summarizeError(err)}`);
639
- }
675
+ // Ensure parent directory exists
676
+ const parentDir = path.dirname(dbPath);
677
+ await fs.mkdir(parentDir, { recursive: true });
678
+ await preflightLbugSidecars(dbPath, {
679
+ mode: 'write',
680
+ logger,
681
+ allowQuarantine: true,
682
+ });
683
+ const opened = await openLbugConnection(lbug, dbPath);
684
+ db = opened.db;
685
+ conn = opened.conn;
686
+ currentDbReadOnly = false;
687
+ }
688
+ finally {
689
+ await releaseInitLock();
640
690
  }
641
- // Ensure parent directory exists
642
- const parentDir = path.dirname(dbPath);
643
- await fs.mkdir(parentDir, { recursive: true });
644
- await preflightLbugSidecars(dbPath, {
645
- mode: readOnly ? 'read-only' : 'write',
646
- logger,
647
- allowQuarantine: true,
648
- });
649
- const opened = readOnly
650
- ? await openLbugConnection(lbug, dbPath, { readOnly: true })
651
- : await openLbugConnection(lbug, dbPath);
652
- const usable = readOnly ? await ensureReadOnlyConnectionUsable(dbPath, opened) : opened;
653
- db = usable.db;
654
- conn = usable.conn;
655
- currentDbReadOnly = readOnly;
656
- }
657
- finally {
658
- await releaseInitLock();
659
691
  }
660
692
  if (!readOnly) {
661
693
  const missingShadowError = await runSchemaCreationQueries(dbPath);
@@ -179,6 +179,7 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
179
179
  ], {
180
180
  cwd: repoPath,
181
181
  stdio: ['ignore', 'pipe', 'ignore'],
182
+ windowsHide: true,
182
183
  encoding: 'utf8',
183
184
  });
184
185
  return out.trim().length > 0;
@@ -26,7 +26,7 @@ export function detectCursorCLI() {
26
26
  if (cachedCursorBin !== undefined)
27
27
  return cachedCursorBin;
28
28
  try {
29
- execSync('agent --version', { stdio: 'ignore' });
29
+ execSync('agent --version', { stdio: 'ignore', windowsHide: true });
30
30
  cachedCursorBin = 'agent';
31
31
  }
32
32
  catch {
@@ -78,6 +78,7 @@ export async function callCursorLLM(prompt, config, systemPrompt, options) {
78
78
  const child = spawn(cursorBin, args, {
79
79
  cwd: config.workingDirectory || process.cwd(),
80
80
  stdio: ['pipe', 'pipe', 'pipe'],
81
+ windowsHide: true,
81
82
  env: {
82
83
  ...process.env,
83
84
  // Ensure non-interactive mode
@@ -675,7 +675,12 @@ export class WikiGenerator {
675
675
  // ─── Helpers ────────────────────────────────────────────────────────
676
676
  getCurrentCommit() {
677
677
  try {
678
- return execSync('git rev-parse HEAD', { cwd: this.repoPath }).toString().trim();
678
+ return execSync('git rev-parse HEAD', {
679
+ cwd: this.repoPath,
680
+ windowsHide: true,
681
+ })
682
+ .toString()
683
+ .trim();
679
684
  }
680
685
  catch {
681
686
  return '';
@@ -690,6 +695,7 @@ export class WikiGenerator {
690
695
  execFileSync('git', ['merge-base', '--is-ancestor', fromCommit, toCommit], {
691
696
  cwd: this.repoPath,
692
697
  stdio: 'ignore',
698
+ windowsHide: true,
693
699
  });
694
700
  return true;
695
701
  }
@@ -706,6 +712,7 @@ export class WikiGenerator {
706
712
  try {
707
713
  const output = execFileSync('git', ['diff', `${fromCommit}..${toCommit}`, '--name-only'], {
708
714
  cwd: this.repoPath,
715
+ windowsHide: true,
709
716
  })
710
717
  .toString()
711
718
  .trim();
@@ -1978,6 +1978,7 @@ export class LocalBackend {
1978
1978
  cwd: diffCwd,
1979
1979
  encoding: 'utf-8',
1980
1980
  maxBuffer: 256 * 1024 * 1024,
1981
+ windowsHide: true,
1981
1982
  });
1982
1983
  }
1983
1984
  catch (err) {
@@ -2195,6 +2196,7 @@ export class LocalBackend {
2195
2196
  timeout: 5000,
2196
2197
  // Avoid ENOBUFS on large repos: rg -l can list many files.
2197
2198
  maxBuffer: 256 * 1024 * 1024,
2199
+ windowsHide: true,
2198
2200
  });
2199
2201
  const files = output
2200
2202
  .trim()
@@ -262,6 +262,7 @@ export function getRemoteOriginUrl(cwd) {
262
262
  const proc = spawn('git', ['config', '--get', 'remote.origin.url'], {
263
263
  cwd,
264
264
  stdio: ['ignore', 'pipe', 'pipe'],
265
+ windowsHide: true,
265
266
  env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
266
267
  });
267
268
  let stdout = '';
@@ -368,6 +369,7 @@ function runGit(args, cwd) {
368
369
  const proc = spawn('git', args, {
369
370
  cwd,
370
371
  stdio: ['ignore', 'pipe', 'pipe'],
372
+ windowsHide: true,
371
373
  env: {
372
374
  ...process.env,
373
375
  // Prevent git from prompting for credentials (hangs the process)
@@ -4,7 +4,11 @@ import path from 'path';
4
4
  // Git utilities for repository detection, commit tracking, and diff analysis
5
5
  export const isGitRepo = (repoPath) => {
6
6
  try {
7
- execSync('git rev-parse --is-inside-work-tree', { cwd: repoPath, stdio: 'ignore' });
7
+ execSync('git rev-parse --is-inside-work-tree', {
8
+ cwd: repoPath,
9
+ stdio: 'ignore',
10
+ windowsHide: true,
11
+ });
8
12
  return true;
9
13
  }
10
14
  catch {
@@ -21,6 +25,7 @@ export const getCurrentCommit = (repoPath) => {
21
25
  // "fatal: not a git repository" to stderr, which leaks to the user's
22
26
  // terminal even though the error is caught here (#1172).
23
27
  stdio: ['ignore', 'pipe', 'ignore'],
28
+ windowsHide: true,
24
29
  })
25
30
  .toString()
26
31
  .trim();
@@ -58,6 +63,7 @@ export const getRemoteUrl = (repoPath) => {
58
63
  raw = execSync('git config --get remote.origin.url', {
59
64
  cwd: repoPath,
60
65
  stdio: ['ignore', 'pipe', 'ignore'],
66
+ windowsHide: true,
61
67
  })
62
68
  .toString()
63
69
  .trim();
@@ -97,6 +103,7 @@ export const getGitRoot = (fromPath) => {
97
103
  cwd: fromPath,
98
104
  // Suppress stderr -- see getCurrentCommit comment and #1172.
99
105
  stdio: ['ignore', 'pipe', 'ignore'],
106
+ windowsHide: true,
100
107
  })
101
108
  .toString()
102
109
  .trim();
@@ -139,6 +146,7 @@ export const getCanonicalRepoRoot = (fromPath) => {
139
146
  const commonDir = execSync('git rev-parse --path-format=absolute --git-common-dir', {
140
147
  cwd: fromPath,
141
148
  stdio: ['ignore', 'pipe', 'ignore'],
149
+ windowsHide: true,
142
150
  })
143
151
  .toString()
144
152
  .trim();
@@ -247,6 +255,7 @@ export const getRemoteOriginUrl = (repoPath) => {
247
255
  const url = execSync('git config --get remote.origin.url', {
248
256
  cwd: repoPath,
249
257
  stdio: ['ignore', 'pipe', 'ignore'],
258
+ windowsHide: true,
250
259
  })
251
260
  .toString()
252
261
  .trim();
@@ -77,6 +77,7 @@ function findCanonicalRepoRoot(cwd) {
77
77
  timeout: 2000,
78
78
  cwd,
79
79
  stdio: ['pipe', 'pipe', 'pipe'],
80
+ windowsHide: true,
80
81
  });
81
82
  if (result.error || result.status !== 0) return null;
82
83
  const commonDir = (result.stdout || '').trim();
@@ -218,6 +219,7 @@ function runGitNexusCli(cliPath, args, cwd, timeout) {
218
219
  timeout,
219
220
  cwd,
220
221
  stdio: ['pipe', 'pipe', 'pipe'],
222
+ windowsHide: true,
221
223
  });
222
224
  }
223
225
  // On Windows, invoke npx.cmd directly (no shell needed)
@@ -226,6 +228,7 @@ function runGitNexusCli(cliPath, args, cwd, timeout) {
226
228
  timeout: timeout + 5000,
227
229
  cwd,
228
230
  stdio: ['pipe', 'pipe', 'pipe'],
231
+ windowsHide: true,
229
232
  });
230
233
  }
231
234
 
@@ -315,6 +318,7 @@ function handlePostToolUse(input) {
315
318
  timeout: 3000,
316
319
  cwd,
317
320
  stdio: ['pipe', 'pipe', 'pipe'],
321
+ windowsHide: true,
318
322
  });
319
323
  currentHead = (headResult.stdout || '').trim();
320
324
  } catch {
@@ -109,6 +109,7 @@ function hasGitNexusServerOwnerWindows(dbPathAbs, myPid) {
109
109
  encoding: 'utf-8',
110
110
  timeout: 6000,
111
111
  stdio: ['ignore', 'pipe', 'ignore'],
112
+ windowsHide: true,
112
113
  env: { ...process.env, GITNEXUS_HOOK_RM_TARGET: dbPathAbs },
113
114
  },
114
115
  );
@@ -192,6 +193,7 @@ function unixLsofPsFindGitNexusServer(dbPathAbs, myPid) {
192
193
  encoding: 'utf-8',
193
194
  timeout: 1000,
194
195
  stdio: ['ignore', 'pipe', 'ignore'],
196
+ windowsHide: true,
195
197
  });
196
198
  if (lsof.error) return lsof.error.code === 'ETIMEDOUT';
197
199
 
@@ -203,6 +205,7 @@ function unixLsofPsFindGitNexusServer(dbPathAbs, myPid) {
203
205
  encoding: 'utf-8',
204
206
  timeout: 500,
205
207
  stdio: ['ignore', 'pipe', 'ignore'],
208
+ windowsHide: true,
206
209
  });
207
210
  if (ps.error) {
208
211
  if (ps.error.code === 'ETIMEDOUT') return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.6-rc.49",
3
+ "version": "1.6.6-rc.50",
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",