Commit graph

3 commits

Author SHA1 Message Date
Gergő Magyar
2a5bbbeaae
fix: make extension installs offline-first (#1161)
* feat(review): add PR reviewer swarm agents

Seven read-only subagents coordinated by an orchestration skill for
structured, evidence-grounded production-readiness PR reviews.

Agents: facts-historian, branch-hygiene, risk-architect, test-ci-verifier,
security-boundary, docs-dod, synthesis-critic. All use Read/Grep/Glob/Bash
only — no edit tools.

Skill invoked as /gitnexus-pr-swarm-review <PR>.

* fix: patch vector extension and uncaughtException for review findings

- Add { policy: 'auto' } to both loadVectorExtension() calls in
  embedding-pipeline.ts so analyze --embeddings auto-installs VECTOR
- Add void to uncaughtException shutdown(1) call for Node v20+ safety
- Re-add getExtensionInstallPolicy export + default change + 4 tests

* fix(mcp,lbug): graceful shutdown exit codes + complete offline-first VECTOR policy

Completes the two live issues PR #1161 only partially addressed.

#1132 — MCP shutdown crash: SIGINT/SIGTERM were registered with `shutdown`
directly, so Node passed the signal NAME string into process.exit(), crashing
with ERR_INVALID_ARG_TYPE ('SIGTERM'). Map signals to numeric exit codes
(SIGINT->130, SIGTERM->143) via a testable installSignalShutdown(); add an
unref'd force-exit watchdog so a hung disconnect()/close() cannot wedge
shutdown; and void the stdin/stdout handlers so event payloads never reach
process.exit() as a non-number.

#1153 — offline-first extension loading:
- semanticSearch (a query/read path) no longer forces policy:'auto'; queries
  use load-only and never spawn a network INSTALL (extension.ladybugdb.com).
- the analyze embedding WRITE path resolves the policy from
  GITNEXUS_LBUG_EXTENSION_INSTALL (honoring never/load-only/auto; default auto)
  instead of hard-forcing 'auto', so an offline/locked-down operator's override
  is respected (the regression that re-broke #1153 for the VECTOR path).
- surface the active install policy in `gitnexus doctor` (was claimed but never
  delivered; also gives the previously-dead getExtensionInstallPolicy a caller).
- emit an actionable message when VECTOR is unavailable.

Tests: regression for the signal->numeric mapping (reproduces the signal-string
crash condition) and for embedding install-policy resolution. tsc/prettier clean,
eslint 0 errors, 55 unit tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(analyze): degrade gracefully when FTS extension is unavailable

The load-only default made `gitnexus analyze` throw when the FTS
extension was not pre-installed, breaking CI and offline use. Make the
analyze write path opt into the `auto` install policy (LOAD-first then
bounded INSTALL — symmetric with the VECTOR/embeddings path and the #726
contract) and degrade gracefully when the extension still cannot load:
skip search-index creation, log a warning, and complete with a fully
queryable graph (only full-text/BM25 search is disabled). `--repair-fts`
still fails loudly.

- Surface the degraded state instead of reporting healthy:
  AnalyzeResult.ftsSkipped, a persistent CLI summary warning, and
  meta.json capabilities.fts.status = "unavailable".
- Skip the FTS-primitive integration tests when the extension is
  unavailable (shared skipUnlessFtsAvailable helper).
- Add a unit test for the degradation branch; fix the existing
  full-analyze test mock that omitted loadFTSExtension.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(lbug): skip FTS-seeding suites when extension is unavailable

The withTestLbugDB helper seeds FTS indexes in beforeAll via createFTSIndex,
which throws when the optional FTS extension cannot load — failing the whole
suite on machines where it is neither pre-installed nor installable (the
macOS platform-sensitive CI runner). Probe the extension once (mirroring the
analyze write path's `auto` policy), bypass FTS seeding when it is
unavailable, and skip the suite's tests via beforeEach with a one-time
warning so the skip is visible rather than a setup crash.

Fixes the macOS failures in search-core, search-pool, local-backend-calltool,
and staleness-and-stability. Suites still run normally where FTS is available.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 20:04:41 +01:00
Gergő Magyar
3f0c74fea0
fix(deps): upgrade @ladybugdb/core to 0.16.0 to resolve native segfaults (#1235)
* fix(deps): upgrade @ladybugdb/core to 0.16.0 to resolve native segfaults

Resolves the SIGSEGV / access-violation (0xC0000005) / exit-139 crashes that
have been reported widely since 1.6.3. The native crashes originate in
@ladybugdb/core 0.15.x — primarily during FTS index creation, VECTOR
extension load, and concurrent query teardown — and are reproducible on
Linux, macOS and Windows. The maintainer-confirmed fix is to bump the
runtime to 0.16.0, which ships nodejs async + memory-management fixes,
extension ABI bump, and macOS Intel binaries.

Adopting 0.16.0 cleanly required three supporting changes; without them
the upgrade itself regresses other paths:

1. maxDBSize must be passed explicitly. 0.16.0 keeps the upstream JSDoc
   note that the default 0 is "introduced temporarily for now to get
   around with the default 8 TB mmap address space limit some
   environment". Constrained CI runners and laptops cannot reserve 8 TB
   and crash with "Buffer manager exception: Mmap for size
   8796093022208 failed." A new gitnexus/src/core/lbug/lbug-config.ts
   centralises a 16 GiB default (overridable via
   GITNEXUS_LBUG_MAX_DB_SIZE) and every Database() construction site
   now passes it.

2. enableCompression default flipped from false to true in 0.16.0. Every
   Database() call site is updated to pass false explicitly so existing
   GitNexus indexes keep the same wire format.

3. Bridge DB sidecar files (.wal, .shadow). 0.16.0 enforces a database-id
   check on .wal / .shadow sidecars and rejects opens whose sidecars
   belong to a different base name. writeBridge now (a) cleans the full
   sidecar set when removing the tmp slot, (b) renames .wal / .shadow
   alongside the main file during the atomic .tmp -> .lbug swap, and
   (c) wraps openBridgeDbReadOnly in a bounded retry on transient
   Win32-Error-33 lock errors. Eager db.init() / conn.init() forces the
   lazy native handle to surface lock contention at the retry site.

Known limitation (not a regression): on Windows the 0.16.0 native binary
does not release the OS file lock until the process exits, so the
close-then-reopen-same-process pattern raises Error 33 after the first
close. Production paths (analyze / serve / mcp each open the DB exactly
once per process) are unaffected, but eight tests that exercise the
pattern are guarded with a process.platform === 'win32' skip; CI's
Linux + macOS shards exercise them as before. Tracking upstream:
kuzudb/kuzu#3872 / #3883 / #4730.

Closes #1136 #1154 #1160 #1162 #1178 #1195 #1196 #1199 #1204 #1206
Refs #1209 (supersedes — Dependabot bump without the supporting fixes)

Made-with: Cursor

* fix(test): isolate LadybugDB native test state

Use per-suite LadybugDB databases in integration helpers so test forks do not reopen a database created by Vitest global setup, and centralize Windows-tolerant native temp cleanup for bridge tests.

* fix(lbug): avoid bridge existence reopen

Reuse the built LadybugDB config in the extension installer and avoid native close/reopen cycles when checking bridge existence on Windows.

Made-with: Cursor

* chore(docs): exclude local lbug plan

Keep the refactor planning note out of the PR while leaving the ignored local copy on disk.

Made-with: Cursor

* refactor(lbug): centralize database construction

Route LadybugDB opens through shared helpers so native constructor defaults stay consistent across core, pool, bridge, and extension install paths.

Made-with: Cursor

---------

Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
2026-04-30 17:40:39 +01:00
Gergő Magyar
ffa0510f9a
fix(lbug): prevent DuckDB extension install hangs (#1129)
Some checks are pending
CI / quality (push) Waiting to run
CI / tests (push) Waiting to run
CI / e2e (push) Waiting to run
CI / scope-parity (push) Waiting to run
CI / Save PR Metadata (push) Blocked by required conditions
CI / CI Gate (push) Blocked by required conditions
Release Candidate / Check if release candidate should run (push) Waiting to run
Release Candidate / ci (push) Blocked by required conditions
Release Candidate / Publish release candidate to npm (push) Blocked by required conditions
Release Candidate / Build & Push RC Docker images (push) Blocked by required conditions
* fix(lbug): bound DuckDB extension install via ExtensionManager (closes #1128)

`gitnexus analyze` could hang indefinitely (60% / 85% on Windows) when
DuckDB's `INSTALL fts` or `INSTALL VECTOR` was unable to reach
`extensions.duckdb.org`. The DuckDB driver's INSTALL is a synchronous
network call, so any blocked egress would block the Node event loop
forever.

Replace the ad-hoc, in-process INSTALL/LOAD scattered across
`lbug-adapter.ts` and `pool-adapter.ts` with a single
`ExtensionManager` that owns the lifecycle of optional DuckDB
extensions:

* `LOAD` is always tried first — per-connection, idempotent, no network.
* If `LOAD` fails and policy permits, INSTALL runs in a short-lived
  child Node process bounded by `GITNEXUS_LBUG_EXTENSION_INSTALL_TIMEOUT_MS`
  (default 15s). The parent loop keeps spinning; on timeout the child is
  killed with SIGKILL and the capability is flagged unavailable.
* Capabilities and install attempts are cached per process, so a single
  bounded install per extension covers every subsequent call.

Install policy is now an explicit, per-context decision:

* `auto` (default for analyze) — try LOAD, fall back to bounded INSTALL.
* `load-only` — used by `pool-adapter` (serve / MCP read paths) so user
  queries never block on a network install.
* `never` — operator escape hatch for offline / airgapped environments.

`createFTSIndex` and `createVectorIndex` now check the boolean return
value before issuing the index DDL, so missing extensions degrade BM25
and semantic search gracefully without ever throwing during analyze.

Tests:
- New unit suite for `ExtensionManager` covering LOAD-first behavior,
  all three policies, install caching, observability, and warn dedup.
- Existing vector-extension integration tests pass against the new
  boolean return type.
- Existing embedding-pipeline mocks updated to return `true`.

Docs: `gitnexus/README.md` documents `GITNEXUS_LBUG_EXTENSION_INSTALL`
and `GITNEXUS_LBUG_EXTENSION_INSTALL_TIMEOUT_MS` with examples for
offline and slow-network environments.

Made-with: Cursor

* fix(lbug): move DuckDB extension install child into script

Keep the bounded out-of-process INSTALL behavior, but replace the inline child code with a stable packaged ESM script. This makes the child process directly runnable and gives debuggable stack traces without source-vs-dist branching or a runtime transpiler.

Made-with: Cursor
2026-04-27 23:09:17 +01:00