gitnexus 1.6.10-rc.14 → 1.6.10-rc.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.
@@ -14,11 +14,12 @@ import v8 from 'v8';
14
14
  import cliProgress from 'cli-progress';
15
15
  import { isLbugReady, LbugWipeError } from '../core/lbug/lbug-adapter.js';
16
16
  import { boundedCheckpointBeforeExit } from '../core/lbug/shutdown-helpers.js';
17
- import { isLbugCheckpointIoError, isWalCorruptionError, parseWalCheckpointThreshold, WAL_RECOVERY_SUGGESTION, } from '../core/lbug/lbug-config.js';
17
+ import { getOsPageSize, isLbugCheckpointIoError, isLbugPageSizeFrameError, isPageSizeAwareLadybug, isWalCorruptionError, parseWalCheckpointThreshold, WAL_RECOVERY_SUGGESTION, } from '../core/lbug/lbug-config.js';
18
18
  import { getStoragePaths, getGlobalRegistryPath, RegistryNameCollisionError, AnalysisNotFinalizedError, assertAnalysisFinalized, } from '../storage/repo-manager.js';
19
19
  import { getGitRoot, hasGitDir, getDefaultBranch } from '../storage/git.js';
20
20
  import { loadAnalyzeConfig, mergeAnalyzeOptions, resolveDefaultBranch, validateBranchName, GitNexusRcError, } from './analyze-config.js';
21
21
  import { runFullAnalysis } from '../core/run-analyze.js';
22
+ import { getRuntimeFingerprint } from '../core/platform/capabilities.js';
22
23
  import { getMaxFileSizeBannerMessage } from '../core/ingestion/utils/max-file-size.js';
23
24
  import { warnMissingOptionalGrammars, getOptionalGrammarExtensions } from './optional-grammars.js';
24
25
  import { glob } from 'glob';
@@ -1252,6 +1253,48 @@ const analyzeCommandImpl = async (inputPath, cliOptions) => {
1252
1253
  process.exitCode = 1;
1253
1254
  return;
1254
1255
  }
1256
+ // Buffer-manager frame-release failure on non-4K-page kernels (#1231).
1257
+ // LadybugDB <= 0.17.x assumed 4 KiB OS pages when releasing evicted
1258
+ // frames; Raspberry Pi 5 (16 KiB kernel pages) and other arm64 systems
1259
+ // crash mid-COPY with a raw native message. 0.18.0 detects the page size
1260
+ // at runtime, so the actionable fix depends on which side of that
1261
+ // boundary the installed @ladybugdb/core is.
1262
+ if (isLbugPageSizeFrameError(err)) {
1263
+ const pageSize = getOsPageSize();
1264
+ const ladybug = getRuntimeFingerprint().ladybugdb;
1265
+ const pageLine = pageSize !== undefined && pageSize !== 4096
1266
+ ? ` Detected OS page size: ${pageSize} bytes (non-4K — e.g. Raspberry Pi 5 16K kernel, Asahi Linux).\n`
1267
+ : '';
1268
+ // The upgrade variant must not assert version facts about an unknown
1269
+ // version — mirror the doctor-side wording rule (#2424 review R2).
1270
+ const upgradeIntro = ladybug === undefined
1271
+ ? ` The installed @ladybugdb/core version is unknown — it may predate the\n` +
1272
+ ` runtime OS-page-size detection added in 0.18.0.\n`
1273
+ : ` The installed @ladybugdb/core (${ladybug}) assumes 4 KiB pages in its buffer\n` +
1274
+ ` manager.\n`;
1275
+ const guidance = isPageSizeAwareLadybug(ladybug)
1276
+ ? ` The installed @ladybugdb/core (${ladybug}) already detects the OS page size at runtime,\n` +
1277
+ ` so this configuration was expected to work. Please report it:\n` +
1278
+ ` https://github.com/abhigyanpatwari/GitNexus/issues/1231\n` +
1279
+ ` and include: gitnexus --version, node --version, getconf PAGE_SIZE, uname -a,\n` +
1280
+ ` and the full error message above.\n`
1281
+ : upgradeIntro +
1282
+ ` Upgrade GitNexus to a release that bundles @ladybugdb/core >= 0.18.0\n` +
1283
+ ` (gitnexus >= 1.6.9), which detects the OS page size at runtime:\n` +
1284
+ ` npm install -g gitnexus@latest\n` +
1285
+ ` Last-resort workaround on Raspberry Pi 5: boot the 4 KiB-page kernel\n` +
1286
+ ` (config.txt: kernel=kernel8.img), at the cost of Pi 5 optimizations.\n`;
1287
+ // Embed the raw native text (indented, no stack) so "the full error
1288
+ // message above" is fulfillable — same idiom as the LbugWipeError
1289
+ // branch. The errno suffix and the 0.18.0 guard's frame/granule numbers
1290
+ // are the discriminating triage content (#2424 review P2).
1291
+ cliError(` LadybugDB's buffer manager failed to release frame memory.\n` +
1292
+ ` ${msg.replace(/\n/g, '\n ')}\n` +
1293
+ pageLine +
1294
+ guidance, { recoveryHint: 'lbug-page-size', pageSize, ladybugVersion: ladybug });
1295
+ process.exitCode = 1;
1296
+ return;
1297
+ }
1255
1298
  // Local embedding runtime unsupported on this platform (macOS Intel ships no
1256
1299
  // darwin/x64 ONNX native binding, #1515). The guard threw before importing
1257
1300
  // transformers.js, so this is a clean, actionable GitNexus message. Checked
@@ -12,7 +12,7 @@ import { type CliMessageKey, type CliMessageVars } from './i18n/index.js';
12
12
  * Consumers can import this type to narrow log-record `recoveryHint`
13
13
  * fields without restating the literal list.
14
14
  */
15
- export type RecoveryHint = 'wal-corruption' | 'wal-checkpoint-threshold' | 'lbug-wipe-failed' | 'heap-oom-respawn' | 'native-worker-abort' | 'hf-endpoint-unreachable' | 'http-embedding-endpoint-error' | 'embedding-dims-invalid' | 'local-embedding-unsupported' | 'local-embedding-stack-missing' | 'large-repo' | 'npm-resolution' | 'module-not-found' | 'gitnexusrc-invalid' | 'default-branch-invalid';
15
+ export type RecoveryHint = 'wal-corruption' | 'wal-checkpoint-threshold' | 'lbug-wipe-failed' | 'lbug-page-size' | 'heap-oom-respawn' | 'native-worker-abort' | 'hf-endpoint-unreachable' | 'http-embedding-endpoint-error' | 'embedding-dims-invalid' | 'local-embedding-unsupported' | 'local-embedding-stack-missing' | 'large-repo' | 'npm-resolution' | 'module-not-found' | 'gitnexusrc-invalid' | 'default-branch-invalid';
16
16
  /**
17
17
  * Common shape for the optional structured-field bag passed to
18
18
  * `cliError`/`cliWarn`/`cliInfo`. Typed so the `recoveryHint` slot is
@@ -23,4 +23,17 @@ export declare function localEmbeddingDoctorStatus(opts: {
23
23
  status: string;
24
24
  detail: string | null;
25
25
  };
26
+ /**
27
+ * Page-size lines for the `doctor` Runtime section (#1231). Pure so the
28
+ * warning gate can be unit-tested without running the whole command (the
29
+ * `localEmbeddingDoctorStatus` precedent above) — but takes the probed
30
+ * values as plain params rather than injectable probes, because `undefined`
31
+ * is a *meaningful* pageSize state here (probe unavailable / win32) and
32
+ * would collide with a "not provided → use default" DI convention.
33
+ *
34
+ * Returns 0 lines (page size unknown), 1 line (page size), or 2 lines
35
+ * (page size + non-4K warning when the installed @ladybugdb/core does not
36
+ * detect the OS page size at runtime).
37
+ */
38
+ export declare function pageSizeDoctorLines(pageSize: number | undefined, ladybugVersion: string | undefined): string[];
26
39
  export declare const doctorCommand: () => Promise<void>;
@@ -5,6 +5,7 @@ import { getLocalEmbeddingRuntimeBlocker, localEmbeddingPrefixUnloadableMessage,
5
5
  import { isPrefixRuntimeLoadable, resolveEmbeddingRuntime, } from '../core/embeddings/runtime-install.js';
6
6
  import { cudaRedirectDoctorStatus } from '../core/embeddings/onnxruntime-node-resolver.js';
7
7
  import { checkLbugNative, probeFtsExtensionLoad } from '../core/lbug/native-check.js';
8
+ import { getOsPageSize, isPageSizeAwareLadybug } from '../core/lbug/lbug-config.js';
8
9
  import { diagnoseExtensionLoad } from '../core/lbug/extension-load-error.js';
9
10
  import { getExtensionInstallPolicy } from '../core/lbug/extension-loader.js';
10
11
  import { t } from './i18n/index.js';
@@ -87,6 +88,33 @@ export function localEmbeddingDoctorStatus(opts) {
87
88
  }
88
89
  return { status: '✓ local embeddings supported', detail: null };
89
90
  }
91
+ /**
92
+ * Page-size lines for the `doctor` Runtime section (#1231). Pure so the
93
+ * warning gate can be unit-tested without running the whole command (the
94
+ * `localEmbeddingDoctorStatus` precedent above) — but takes the probed
95
+ * values as plain params rather than injectable probes, because `undefined`
96
+ * is a *meaningful* pageSize state here (probe unavailable / win32) and
97
+ * would collide with a "not provided → use default" DI convention.
98
+ *
99
+ * Returns 0 lines (page size unknown), 1 line (page size), or 2 lines
100
+ * (page size + non-4K warning when the installed @ladybugdb/core does not
101
+ * detect the OS page size at runtime).
102
+ */
103
+ export function pageSizeDoctorLines(pageSize, ladybugVersion) {
104
+ if (pageSize === undefined)
105
+ return [];
106
+ const lines = [` ${padDisplayEnd('page size', 10)}${pageSize}`];
107
+ if (pageSize > 4096 && !isPageSizeAwareLadybug(ladybugVersion)) {
108
+ // Don't assert "< 0.18.0" as fact when the version is unresolvable
109
+ // (#2424 review R2) — name the unknown state instead.
110
+ const versionClause = ladybugVersion === undefined
111
+ ? 'an unknown @ladybugdb/core version (may predate 0.18.0)'
112
+ : `@ladybugdb/core < 0.18.0`;
113
+ lines.push(` ${padDisplayEnd('', 10)}⚠ non-4K page size with ${versionClause} — ` +
114
+ `'gitnexus analyze' may fail during COPY (#1231). Upgrade gitnexus (npm install -g gitnexus@latest).`);
115
+ }
116
+ return lines;
117
+ }
90
118
  export const doctorCommand = async () => {
91
119
  const fingerprint = getRuntimeFingerprint();
92
120
  const capabilities = getRuntimeCapabilities();
@@ -97,6 +125,13 @@ export const doctorCommand = async () => {
97
125
  console.log(` ${label('doctor.labels.node', 10)}${fingerprint.node}`);
98
126
  console.log(` ${label('doctor.labels.gitnexus', 10)}${fingerprint.gitnexus}`);
99
127
  console.log(` ${label('doctor.labels.ladybugdb', 10)}${fingerprint.ladybugdb ?? 'unknown'}`);
128
+ // OS page size next to the LadybugDB version because the two interact:
129
+ // @ladybugdb/core < 0.18.0 assumed 4 KiB pages in its buffer manager and
130
+ // crashes mid-COPY on 16 KiB/64 KiB-page kernels (#1231). Literal label
131
+ // (like the 'native' line below) to avoid adding i18n keys.
132
+ for (const line of pageSizeDoctorLines(getOsPageSize(), fingerprint.ladybugdb)) {
133
+ console.log(line);
134
+ }
100
135
  const nativeCheck = checkLbugNative();
101
136
  if (nativeCheck.ok) {
102
137
  console.log(` ${padDisplayEnd('native', 10)}✓ lbugjs.node loaded`);
@@ -62,6 +62,31 @@ export declare function isWalCorruptionError(err: unknown): boolean;
62
62
  * back to the permissive matcher.
63
63
  */
64
64
  export declare const isLbugCheckpointIoError: (err: unknown) => boolean;
65
+ /**
66
+ * True when `err` looks like the LadybugDB buffer manager failing to release
67
+ * frame memory — the failure mode of a 4 KiB page-size assumption on a
68
+ * 16 KiB/64 KiB-page kernel (#1231). Deliberately does NOT match the
69
+ * generic "buffer pool is full" exhaustion error, which is a sizing
70
+ * problem, not a page-size one.
71
+ */
72
+ export declare const isLbugPageSizeFrameError: (err: unknown) => boolean;
73
+ /**
74
+ * True when the given `@ladybugdb/core` version contains the runtime
75
+ * OS-page-size detection introduced in 0.18.0 (see the matcher comment
76
+ * above). Unknown/unparseable versions return false so callers err on the
77
+ * side of showing the upgrade hint.
78
+ */
79
+ export declare const isPageSizeAwareLadybug: (version: string | undefined) => boolean;
80
+ /**
81
+ * OS memory page size in bytes, or `undefined` when it cannot be determined
82
+ * (Windows, missing getconf, sandboxed exec). Node exposes no page-size API,
83
+ * so this shells out to POSIX `getconf PAGE_SIZE` — same execFileSync shape
84
+ * as the Windows 8.3 short-path probe above, but with a tighter timeout and
85
+ * an explicit killSignal (see the options comment below).
86
+ */
87
+ export declare const getOsPageSize: () => number | undefined;
88
+ /** Exported only for unit tests — clears the getconf probe cache. */
89
+ export declare const _resetOsPageSizeCacheForTest: () => void;
65
90
  type LbugModule = typeof lbug;
66
91
  export interface LbugDatabaseOptions {
67
92
  readOnly?: boolean;
@@ -337,6 +337,106 @@ export const isLbugCheckpointIoError = (err) => {
337
337
  return true;
338
338
  return LBUG_CHECKPOINT_PERMISSIVE_RE.test(msg);
339
339
  };
340
+ // ─── Ladybug non-4K page-size frame-release matcher (#1231) ─────────────────
341
+ //
342
+ // LadybugDB <= 0.17.x hardcoded a 4 KiB OS-page assumption in its buffer
343
+ // manager: evicting a frame released physical memory with
344
+ // `madvise(frame, frameSize, MADV_DONTNEED)` on 4 KiB-aligned frame
345
+ // addresses (verified by disassembling `VMRegion::releaseFrame` in
346
+ // @ladybugdb/core-linux-arm64 0.17.1 — `mov w2, #0x4` = MADV_DONTNEED,
347
+ // throw on non-zero return). On kernels with 16 KiB pages (Raspberry Pi 5
348
+ // default 2712 kernel, Asahi Linux) or 64 KiB pages (some enterprise arm64
349
+ // distros), madvise rejects addresses that are not multiples of the real
350
+ // page size with EINVAL, surfacing as:
351
+ // "Buffer manager exception: Releasing physical memory associated with a
352
+ // frame failed with error code -1: Invalid argument."
353
+ // which aborts `gitnexus analyze` mid-COPY.
354
+ //
355
+ // @ladybugdb/core 0.18.0 rewrote the release path with runtime OS-page-size
356
+ // detection and discard-granule-aligned madvise (new binary strings:
357
+ // "Failed to detect the operating system page size.", "Unsupported page
358
+ // size combination: frame size {}, discard granule size {}, frame group
359
+ // size {}."), so upgrading is the fix. The residual 0.18.0 guard
360
+ // ("Unsupported page size combination") is matched here too so exotic
361
+ // configurations receive the same actionable guidance instead of a raw
362
+ // native message.
363
+ const LBUG_FRAME_RELEASE_RE = /releasing physical memory associated with a frame failed/i;
364
+ const LBUG_PAGE_COMBO_RE = /unsupported page size combination/i;
365
+ /**
366
+ * True when `err` looks like the LadybugDB buffer manager failing to release
367
+ * frame memory — the failure mode of a 4 KiB page-size assumption on a
368
+ * 16 KiB/64 KiB-page kernel (#1231). Deliberately does NOT match the
369
+ * generic "buffer pool is full" exhaustion error, which is a sizing
370
+ * problem, not a page-size one.
371
+ */
372
+ export const isLbugPageSizeFrameError = (err) => {
373
+ if (!err)
374
+ return false;
375
+ const msg = err instanceof Error ? err.message : String(err);
376
+ return LBUG_FRAME_RELEASE_RE.test(msg) || LBUG_PAGE_COMBO_RE.test(msg);
377
+ };
378
+ /**
379
+ * True when the given `@ladybugdb/core` version contains the runtime
380
+ * OS-page-size detection introduced in 0.18.0 (see the matcher comment
381
+ * above). Unknown/unparseable versions return false so callers err on the
382
+ * side of showing the upgrade hint.
383
+ */
384
+ export const isPageSizeAwareLadybug = (version) => {
385
+ if (!version)
386
+ return false;
387
+ const m = /^(\d+)\.(\d+)/.exec(version.trim());
388
+ if (!m)
389
+ return false;
390
+ const major = Number(m[1]);
391
+ const minor = Number(m[2]);
392
+ return major > 0 || minor >= 18;
393
+ };
394
+ // `undefined` = not probed yet; `null` = probed and unavailable. Cached
395
+ // because analyze error paths and doctor may both ask, and getconf forks.
396
+ let cachedOsPageSize;
397
+ /**
398
+ * OS memory page size in bytes, or `undefined` when it cannot be determined
399
+ * (Windows, missing getconf, sandboxed exec). Node exposes no page-size API,
400
+ * so this shells out to POSIX `getconf PAGE_SIZE` — same execFileSync shape
401
+ * as the Windows 8.3 short-path probe above, but with a tighter timeout and
402
+ * an explicit killSignal (see the options comment below).
403
+ */
404
+ export const getOsPageSize = () => {
405
+ if (cachedOsPageSize !== undefined)
406
+ return cachedOsPageSize ?? undefined;
407
+ if (process.platform === 'win32') {
408
+ // Windows allocation granularity is not what madvise alignment is about;
409
+ // the #1231 failure mode is POSIX-only.
410
+ cachedOsPageSize = null;
411
+ return undefined;
412
+ }
413
+ try {
414
+ // killSignal SIGKILL (first use in this repo): the default SIGTERM is
415
+ // catchable, so a signal-trapping child held the "5s" timeout for 9s in
416
+ // review reproduction — SIGKILL makes the timeout real for everything
417
+ // except a child stuck in uninterruptible I/O (D state). 2000ms, not
418
+ // 5000: doctor runs this probe on its happy path and real getconf
419
+ // answers in ~2ms, but keep margin for loaded Pi-class hardware — a
420
+ // too-tight ceiling would silently drop the very #1231 diagnostics this
421
+ // probe exists to provide (the catch caches the failure). (#2424 review)
422
+ const out = execFileSync('getconf', ['PAGE_SIZE'], {
423
+ encoding: 'utf-8',
424
+ timeout: 2000,
425
+ killSignal: 'SIGKILL',
426
+ stdio: ['ignore', 'pipe', 'pipe'],
427
+ });
428
+ const parsed = Number(out.trim());
429
+ cachedOsPageSize = Number.isInteger(parsed) && parsed > 0 ? parsed : null;
430
+ }
431
+ catch {
432
+ cachedOsPageSize = null;
433
+ }
434
+ return cachedOsPageSize ?? undefined;
435
+ };
436
+ /** Exported only for unit tests — clears the getconf probe cache. */
437
+ export const _resetOsPageSizeCacheForTest = () => {
438
+ cachedOsPageSize = undefined;
439
+ };
340
440
  /**
341
441
  * Return true when the error message indicates that a LadybugDB write
342
442
  * transaction could not proceed due to lock contention — either a file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.10-rc.14",
3
+ "version": "1.6.10-rc.16",
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",
@@ -36,6 +36,10 @@ const PLATFORM_LOGIC = [
36
36
  // must exercise the Windows backslash branch, so run it on the OS matrix (#2394).
37
37
  'test/unit/cli-entry.test.ts',
38
38
  'test/unit/platform-capabilities.test.ts',
39
+ // getconf page-size probe: explicit process.platform gate (win32 short-circuit)
40
+ // plus a live-probe test whose only real non-4K coverage is macos-arm64's
41
+ // 16 KiB pages — the exact hardware class #1231 targets (#2424 review).
42
+ 'test/unit/lbug-config-pagesize.test.ts',
39
43
  'test/unit/worker-pool-windows-quarantine.test.ts',
40
44
  'test/unit/lbug-pool-fts-load.test.ts',
41
45
  'test/unit/repo-manager.test.ts',