* fix(lbug): probe-then-load FTS extension on Windows (#1690)
The Windows skip-on-process.platform==='win32' guard in pool-adapter.ts
hard-skipped loadFTSExtension() for every Windows host, even when the
FTS extension binary was already present locally at
~/.lbdb/extension/<version>/win_amd64/fts/libfts.lbug_extension.
That left BM25 silently degraded on Windows hosts that had a working
extension on disk, with no error path — `gitnexus doctor` still reported
FTS as available, but query returned 0 BM25 hits.
This patch adds hasLocalWinFtsExtension() which probes
~/.lbdb/extension/*/win_amd64/fts/ before the Windows skip. When a binary
is on disk we call loadFTSExtension(..., { policy: 'load-only' }); the
crashing install path documented in #1199 / #1217 is never exercised at
query time, and LadybugDB's version-specific resolution combined with
the ExtensionManager's tryLoad try/catch handles stale or zero-byte
sibling version dirs cleanly (no dlopen attempted on a stale binary).
When no binary is on disk at all, we fall back to the upstream skip so
install-time SIGSEGV continues to be avoided.
Verified on Windows 10 + Node 22.19.0 + gitnexus 1.6.5 +
@ladybugdb/core 0.16.1 with the FTS extension cached at 0.16.0:
* BM25 timing goes from 0 → ~250-326ms on previously-zero queries
* gitnexus context / impact / cypher unaffected
* Adversarial-mixed-state run (real 0.16.0 binary + zero-byte stubs at
0.15.0, 0.16.1, 0.17.0): exits 0, no SIGSEGV, FTS resolves to the
real 0.16.0 binary, BM25 returns real hits
* Stub-only state at the resolution path (0.16.0, zero-byte): exits 0,
emits "FTS extension unavailable; load-only policy: extension not
pre-installed", FTS marked unavailable cleanly via markUnavailable
in extension-loader.ts — no silent greenlight
Closes#1690
* test(lbug): cover hasLocalWinFtsExtension probe + format pool-adapter
- Export hasLocalWinFtsExtension and add lbug-pool-win-fts-probe.test.ts
with 7 cases against a real tmpdir + os.homedir spy:
* missing ~/.lbdb/extension dir -> false
* extension root present but no version dirs -> false
* one version dir with binary present -> true
* zero-byte stub at probe path -> true (LOAD failure handled downstream)
* multi-version with binary only in a non-first dir -> true
* multi-version with no binary anywhere (Nix/Bazel/MDM tree) -> false
* fs.readdir throws (EACCES) -> false
The Windows conditional in doInitLbug / initLbugWithDb is intentionally
not unit-isolated: it reduces to `probe ? load : true` over a fully
constructed lbug.Database + Connection pool, which the
test/integration/lbug-pool*.test.ts suites already exercise on the
windows-latest CI matrix.
- Apply prettier format to the fs.stat() call in pool-adapter.ts,
resolving the quality/format CI failure surfaced by gitnexus/autofix.
Addresses DoD §2.7 test-coverage blocker raised in the production-
readiness review on #1692, and the dir-exists-no-file regression case
raised on #1690.
Refs #1690.
---------
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Using --ignore-scripts with npm ci skips per-package install hooks,
including @ladybugdb/core's install.js that copies lbugjs.node into
the correct path. Without it, process.dlopen fails with ERR_DLOPEN_FAILED.
.scrollbar-thin was replaced by global *::-webkit-scrollbar rules in
index.css. The 8 className references across 5 components became dead
code — the global rules apply regardless. Removing them keeps the
codebase consistent with the stylesheet and eliminates the misleading
implication that per-element scrollbar customisation is active.
TOCTOU (CWE-367): fs.statSync(entry.from).isDirectory() was called
before mirrorDirectory/cpSync without error handling. If the path
disappears between stat and use, the subsequent operation throws
unhandled. Wrap in try/catch and continue on error.
Indirect uncontrolled command line (CWE-78): appBuilderLibVersion is
read from desktopPackageLock (an external file) and embedded directly
in an npm pack CLI argument. Validate it matches a semver pattern
before use to prevent injection via a compromised lockfile.
repairGitNexusPackages used npm install --no-package-lock in a temp
directory, resolving all transitive dependencies freely from the live
npm registry. Each packaged build could therefore embed a different
transitive dependency graph, making builds non-reproducible and
vulnerable to a compromised transitive package.
Copy gitnexus/package-lock.json into the temp directory before
running npm ci so transitive deps are pinned to the versions already
resolved during workspace installation.
On macOS, if the backend process crashes while all windows are closed,
createWindow() on the next dock-click loaded the embedded UI against a
dead server — resulting in a silent blank page with no error dialog.
Call ensureGitNexusServerStarted() before createWindow() in the activate
handler so the server is restarted (or verified healthy) before the window
attempts to load content.
ipKeyGenerator('') returned '' as the rate-limit key when both req.ip and
req.socket?.remoteAddress were undefined, silently merging all anonymous
requests into an empty-string bucket instead of the intended 'unknown' sentinel.
Switch to a conditional: only call ipKeyGenerator when an IP is present,
otherwise fall back to 'unknown'. Restores the IPv6 /56 normalisation
comment dropped in aa36869 (see issue #1360).
* feat(eval-server): added --host for user configured host IP instead of system hardcoded IP (127.0.0.1)
* fix(eval-server): localhost value in --host now returns 127.0.0.1 instead of the raw input to fix wrong address, handled error for ipv6 disabled containers
* feat(eval-server): add --host flag with validation and error handling
Co-Authored-By: Val Vladescu <val.vladescu@thirdbridge.com>
* fix(eval-server): bracketed IPv6 addresses to remove ambiguity
* docs(eval-server): document --host flag, READY signal format, and parser migration note
* fix(eval-server): use actual bound port in READY signal; strengthen --host e2e tests
Co-Authored-By: Val Vladescu <val.vladescu@thirdbridge.com>
* feat(eval): wire eval-server --host through gitnexus_docker.py
* docs(eval): added guidance for docker user
* docs(eval): revise the imprecise documentation
* fix(e2e): updated original stdout for new format
* perf(scope-resolution): use owner-keyed lookup for Step 2 member resolution (#1656)
* chore(autofix): apply prettier + eslint fixes via /autofix command
* fix(scope-resolution): index Const/Static in FieldRegistry for Step 2 lookup
Extend FieldRegistry to hold multiple defs per (owner, name), reconcile Const and Static into the owner-keyed index, and wire lookupAllByOwner through the production hook so Step 2 does not drop field kinds the registry never indexed. Pass explicitReceiver on read/write reference sites and document undefined-vs-empty hook semantics for defs fallback.
Co-authored-by: Cursor <cursoragent@cursor.com>
* perf(scope-resolution): centralize O(1) owned-member hook and guard hot path
Extract lookupOwnedMembersByOwner for the production Step 2 hook so merges stay O(1) per registry with no defs.byId scan. Add a perf-contract unit test that throws if byId.values runs when the hook is wired. Reuse a frozen empty sentinel on double miss to avoid per-probe allocations.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: drop unused buildFieldRegistry import
* chore(scope-resolution): apply ce-code-review safe_auto fixes
- Drop unreachable return + unused values() capture in perf-contract trap (Finding #7)
- Type lookupOwnedMembersByOwner ownerDefId as DefId (Finding #9)
- Add Static-kind Step 2 lookup test mirroring the Const case (Finding #11)
* docs(field-registry): document lookupFieldByOwner first-wins semantics
Audit of all 6 production callers (call-processor.ts:2279, walkers.ts:535,
receiver-bound-calls.ts:380+730, type-env.ts:627+631) confirms none depends
on last-wins precedence — all treat the return as a generic 'field with
this name owned by this class'. Clarify the JSDoc to surface the semantic
change introduced when FieldRegistry moved from last-wins to append-order
storage (ce-code-review finding #2).
* test(scope-resolution): extend Step 2 perf contract to implicit-self, MRO, field paths
Adds three sibling tests under the Step 2 perf contract describe block, each
asserting defs.byId.values() does NOT execute when ownedMembersByOwner is wired:
- implicit-self receiver via typeBindings.self (no explicitReceiver branch)
- 2-level MRO chain (Child extends Parent, save resolves on Parent at depth 1)
- FieldRegistry read via Step 2 (property lookup, separate registry path)
Pins the perf invariant on every distinct entry into walkReceiverTypeBinding
so a regression bypassing the hook on any sub-path now fails CI immediately
(ce-code-review finding #8).
* test(resolve-references): cover arity-overload filtering via resolveReferenceSites
Pins the orchestration-layer wiring of providers.arityCompatibility:
hook returns [save(arity 1), save(arity 2)], referenceSite.arity = 1,
arityCompatibility verdicts 'compatible'/'incompatible' by parameterCount,
exactly one reference emitted with toDef = the arity-1 overload.
registries.test.ts already covered arity at the buildMethodRegistry level;
this adds the missing entry-point check that resolveReferenceSites threads
providers correctly through to lookupCore.Step5 (ce-code-review finding #10).
* test(resolve-references): add hook-on vs hook-off parity test
Runs resolveReferenceSites twice on the same fixture (Parent.save method
hit + Child.name field hit, Child extends Parent MRO chain) — once with
ownedMembersByOwner wired to a synthetic registry, once with the hook
absent so collectOwnedMembers takes the defs.byId fallback. Asserts:
- stats are identical (sitesProcessed / referencesEmitted / unresolved)
- referenceIndex.bySourceScope entries have equal length
- toDef sets are equal
- each per-site reference (including evidence and depth) is .toEqual
Locks the semantic-parity claim in code while both paths still exist.
Will be removed alongside the fallback in finding #1 (ce-code-review #3).
* test(typescript): probe Step 2 MRO walk against ambient (declare class) base
Adds typescript-ambient-base-class fixture with an export declare class
AmbientBase + Derived extends AmbientBase and a call site d.ambientMethod().
Integration assertions:
- Both classes are detected
- EXTENDS edge Derived → AmbientBase emitted
- CALLS edge to ambient.ts:ambientMethod resolved via MRO walk
Probes the ce-code-review #6 concern that ambient-only owners (whose
bodies are never parsed) might be silently skipped by Step 2 after the
owner-keyed lookup change. Result: the call resolves correctly — the
method signature inside the declare class body still flows through
reconcileOwnership into model.methods, so the hook returns the right
ancestor hits. Residual risk is empirically closed.
* feat(scope-resolution): route nested types via owner-keyed TypeRegistry
Closes the Step 2 contract footgun where 'hook returns [] = authoritative
miss' silently dropped any owned def whose NodeLabel was outside the
method/field if-chain in reconcileOwnership.
- TypeRegistry: add nestedByOwner Map + lookupAllByOwner(owner, simple)
+ registerByOwner(owner, simple, def). Mirrors MethodRegistry/
FieldRegistry shape; cleared with the rest on cascade clear.
- reconcileOwnership: route class-like NodeLabels (Class/Interface/Enum/
Struct/Union/Trait/TypeAlias/Typedef/Record/Delegate/Annotation/
Template/Namespace) via types.registerByOwner. New nestedTypesRegistered
stat. Idempotent skip via nodeId match.
- validateOwnershipParity: extend the I9 invariant check to nested types.
- lookupOwnedMembersByOwner: merge methods + fields + nested-type hits;
short-circuit when any one source contributes the full result.
Unblocks future receiver-MRO registries that need to resolve 'Outer.Inner'
through the receiver's type-binding chain (ce-code-review finding #5a).
* refactor(scope-resolution): make ownedMembersByOwner required; delete byId fallback
Per ce-code-review finding #1, the optional-hook design encoded a silent
O(|defs|) perf cliff into the type system: any RegistryContext built
without the hook regressed Step 2 to scanning every def per probe with
no warning. Production wires the hook unconditionally; the fallback was
exercised only by tests.
- RegistryContext.ownedMembersByOwner: required, returns readonly
SymbolDefinition[] (no | undefined). Implementations MUST return [] on
authoritative miss.
- collectOwnedMembers in lookup-core.ts collapses to a one-line forward
to the hook; the defs.byId.values() scan and simpleNameOf helper are
deleted (simpleNameOf had no other consumers).
- ResolveReferencesInput.ownedMembersByOwner: required to match.
- Tests: drop three fallback-path tests (registries Const fallback,
resolveReferenceSites no-hook fallback, resolveReferenceSites Const-
undefined fallback) and the hook-vs-fallback parity test added by
finding #3. makeCtx in registries.test.ts now defaults to a real
owner-keyed scan over the test fixture defs so tests that don't care
about the hook keep working.
* perf(free-call-fallback): cache global callables by simple name once per pass
pickUniqueGlobalCallable scanned scopes.defs.byId.values() on every
free-call fallback site. After PR #1656 fixed Step 2, this scan became
the dominant remaining O(|defs|) hot path on large repos (ce-code-review
finding #4).
- buildGlobalCallableIndex builds a Map<simpleName, SymbolDefinition[]>
over scopes.defs once at the top of emitFreeCallFallback. Same filter
the per-site scan applied: Function / Method / Constructor, keyed by
the last .-segment of qualifiedName.
- pickUniqueGlobalCallable consumes the prebuilt index via O(1) Map.get
instead of iterating every def. Per-site complexity drops from
O(|defs|) to O(|defs with this simple name|).
- Cost: O(|defs|) once per pass instead of O(|defs| * |free-call sites|).
Subsequent narrowing (arity, conversion-rank) and the model-side fallback
(model.symbols.lookupCallableByName + model.methods.lookupMethodByName)
are unchanged.
* chore(autofix): apply prettier + eslint fixes via /autofix command
* ci: trigger build
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Gergo Magyar <abhigyan1.patwari@gmail.com>
* feat(cpp): SFINAE-aware overload filter — drops candidates whose enable_if_t / requires constraints fail (#1579)
* fix(cpp): SFINAE follow-ups for is_integral_v/is_arithmetic_v bool and char support, an unqualified F1 test fixture, and parameter-lookup gap documentation (#1579) -> claude feedback
* revert: reverting all changes to .md files
* feat(cpp): add standard-conversion-sequence ranking to overload resolution (#1578)
Introduce `ConversionRankFn` abstraction and `cppConversionRank` implementation
to disambiguate C++ overloaded calls by argument-to-parameter conversion cost.
Exact type match (rank 0) beats standard arithmetic conversion (rank 2), which
beats non-viable mismatch (Infinity). Thread the rank function through
`narrowOverloadCandidates`, `pickImplicitThisOverload`, `pickOverload`, and
`pickUniqueGlobalCallable` via the `ScopeResolver.conversionRankFn` contract.
Add `findAllCallableBindingsInScope` scope walker for collecting all overloads
at the first binding scope. Guard against false ambiguity suppression when
candidates span different files (local-shadows-import preservation).
* fix: address Claude review findings on conversion-rank PR
Finding 1 (HIGH): add tests that exercise the conversion ranker.
- p('a') with p(int)/p(double): char→int promotion (rank 1) beats
char→double conversion (rank 2), forcing step 4b in
narrowOverloadCandidates. Exact-type filter misses both overloads.
- h(42, 2.5) with h(int,int)/h(double,double): multi-arg tied total
score forces the ranker, both candidates score 2 → suppressed.
Finding 2 (HIGH): unify multi-candidate suppression across all paths.
- Non-ADL free-call: suppress when narrowed.length > 1 (same-file
guard), mirroring ADL merged-candidate behavior.
- ADL ordinary-only: same pattern.
- pickOverload: return OVERLOAD_AMBIGUOUS when candidates.length > 1
after normalized-ambiguity check.
- Case 0.5 (this receiver): set ambiguous=true when narrowed > 1.
Finding 3+4 (MEDIUM): implement rank-1 integral promotions.
- char→int and bool→int now return rank 1 (ISO C++ [conv.prom]).
- Updated comment to remove misleading ISO table header; document
only the post-normalization ranking that is actually implemented.
- Updated ConversionRankFn JSDoc in overload-narrowing.ts.
218/218 C++ tests pass (registry-primary). Legacy: 186+32.
* fix: implement pairwise dominance comparison for overload ranking
Replace the summed per-slot conversion cost with ISO C++-aligned
pairwise dominance comparison ([over.ics.rank]). F1 is better than
F2 only when F1 is not worse for every argument and strictly better
for at least one. Non-dominated candidates are returned; if multiple
remain they are genuinely ambiguous.
This fixes false CALLS edges for asymmetric multi-arg overloads:
h('a', 2.5) against h(int,int) / h(double,double) — the old summed
cost picked h(double,double) (cost 2 < 3), but ISO C++ considers
the call ambiguous because h(int,int) is better at arg 0 via char
promotion. The pairwise check correctly finds neither dominates.
Add h('a', 2.5) test case asserting zero CALLS edges alongside
the existing h(42, 2.5) symmetric-tie test.
218/218 C++ tests pass (registry-primary). Legacy: 186+32.
* docs: update step 4b JSDoc to reflect pairwise dominance
---------
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
* Initial plan
* fix: add time-based deadline to cross-file type propagation to prevent stalling on large repos
Adds a 2-minute wall-clock time limit (DEFAULT_CROSS_FILE_ELAPSED_MS) to
runCrossFileBindingPropagation. When exceeded, the phase gracefully stops
and logs a warning. Users can override via GITNEXUS_CROSS_FILE_TIMEOUT_MS
env var. This prevents the analyze command from stalling for hours on very
large repositories where per-file re-resolution is expensive.
Fixes the reported issue where gitnexus analyze stalls at "Cross-file type
propagation" for several hours on repos with 15000+ files.
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/b8341947-557c-4111-a3a8-991ba455ab01
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* fix: root cause - cache tree-sitter queries across files, add live progress reporting
Root cause: cross-file propagation called processCalls() with 1 file at a time,
causing Parser.Query to be recompiled from the query string for every single file
(O(N) compilations vs O(1) for the whole phase). Additionally, progress was only
reported once at the start, making the phase appear completely frozen.
Fixes:
- Add optional `compiledQueryCache` parameter to `processCalls` so callers that
invoke it with single-file batches can share compiled query objects across calls.
The cross-file phase now compiles each language's query string exactly once and
reuses it for all files of that language (e.g. 1 TypeScript compile for 595+ files).
- Pre-count candidate files and emit onProgress every 25 files showing
"Cross-file type propagation (N/M files)..." so the UI shows real movement
instead of a frozen bar.
- Keep the wall-clock deadline (GITNEXUS_CROSS_FILE_TIMEOUT_MS) as a safety
net for pathological inputs.
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/f5028cc8-4bc9-4309-8ffb-798fe2bd7a0a
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* fix: address code review - use SupportedLanguages key type, rename queryCache to compiledQueryCache
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/f5028cc8-4bc9-4309-8ffb-798fe2bd7a0a
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* chore(autofix): apply prettier + eslint fixes via /autofix command
* fix(cross-file): remove wall-clock timeout from type propagation
The query compilation cache and live progress reporting address the
original stall; the 2-minute deadline could truncate cross-file work on
large repos. MAX_CROSS_FILE_REPROCESS (2000) remains as the only cap.
* test(cross-file): verify compiledQueryCache is shared across all processCalls invocations
Finding 1: O(N) query recompilation was fixed by sharing a compiledQueryCache Map
across all processCalls invocations in runCrossFileBindingPropagation. This test
verifies the fix is correctly wired: the same Map instance is passed as the
12th argument to every call, proving queries are compiled once per language,
not once per file.
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/3ab768d9-3993-4882-9d8f-17f7fcbd086e
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* test(cross-file): verify live progress events are emitted with N/M format
Finding 2: frozen progress display was fixed by emitting onProgress every 25 files
with "Cross-file type propagation (N/M files)..." messages instead of calling it
once at phase start. This test verifies the fix with 50 candidate files: expects
onProgress called 3 times (1 initial + at 25 + at 50) with correct N/M counters.
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/3ab768d9-3993-4882-9d8f-17f7fcbd086e
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* fix(cross-file): skip registry-primary language files before readFileContents
Finding 3 (from comment 4466231612): cross-file-impl was calling processCalls
for every candidate file even when that file's language is registry-primary
(TypeScript, C++, Python, Go, C#, PHP, C — since AGENTS.md v1.7.0). processCalls
would immediately skip those files via its own isRegistryPrimary guard, but
cross-file-impl still paid the full cost: readFileContents I/O, buildImportedReturnTypes,
buildImportedRawReturnTypes, and Map allocation — all discarded.
Fix: check isRegistryPrimary(lang) in both the totalCandidates pre-count loop
and the levelCandidates builder, before any file I/O or map building. This
eliminates 595+ no-op processCalls invocations on large TypeScript repos.
Test: mocks isRegistryPrimary to always return true and verifies that
processCalls is never invoked and result is 0. The mock also defaults to false
in beforeEach so existing tests using .ts files are unaffected.
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/3ab768d9-3993-4882-9d8f-17f7fcbd086e
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* refactor(test): address code review - simplify mock factory, name the arg index constant
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/3ab768d9-3993-4882-9d8f-17f7fcbd086e
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
PR #1627's npm install -g npm@latest step crashed mid-install with MODULE_NOT_FOUND: promise-retry — a known fragility when npm self-upgrades. Node 22's bundled npm is 10.9.x (no OIDC). Fix: bump publish job's node-version to 24, which ships with npm 11.x natively. Package consumers unaffected (this Node version is only used during publish; engines.node is >=22.0.0; ci-tests.yml continues testing on Node 22).
First live-fire RC publish after #1610 failed at npm publish with E404. The if: failure() cleanup correctly auto-deleted the partial v-tag and rc-marker, but OIDC never engaged. Root cause: two coordinated upstream bugs.
1. actions/setup-node@v6 with registry-url: writes _authToken into the runner .npmrc AND exports NODE_AUTH_TOKEN from its token: input (defaulting to github.token). npm publish sends GITHUB_TOKEN as the bearer and the registry returns 404. OIDC never tried because npm thinks it already has a credential. See actions/setup-node#1440.
2. The Node 22 runner ships with npm 10.9.x. npm Trusted Publishing OIDC support requires npm >= 11.5.1.
Fix: omit registry-url: from the setup-node step (per the consensus workaround in community discussion #176761), and add npm install -g npm@latest before publish. --provenance flag is NOT added; npm auto-attaches provenance under Trusted Publishing.
Sources:
- https://github.com/actions/setup-node/issues/1440
- https://github.com/orgs/community/discussions/176761
- https://docs.npmjs.com/trusted-publishers/