Commit graph

730 commits

Author SHA1 Message Date
Abhigyan Patwari
9efc6bfcad
fix(lbug/analyze): atomic index swap + read-pool staleness invalidation (#2614)
* fix(lbug): re-open the read pool when analyze rebuilds the index under it

The MCP read pool's initLbug early-returned on an existing pool entry with no
freshness check, so after analyze rebuilt or mutated the on-disk index the
pool kept serving the old (POSIX: unlinked-but-open) inode until LRU/idle
eviction — a silent stale-read window of up to IDLE_TIMEOUT_MS (5 min).

Record the file identity {ino, mtimeMs, size} on each PoolEntry at open, and
re-stat in initLbug: unchanged → reuse; changed & idle → closeOne + reopen the
new file; changed while a query is in flight → serve the current handle (a
later idle initLbug reopens, since closing an in-use connection is a native
use-after-free). A stat failure (ENOENT during a full rebuild's unlink window)
is treated as unchanged so the reader keeps its valid open inode until the new
file appears. Mirrors the bridge cache's mtime-invalidation pattern.

Step 1 of docs/plans/2026-07-21-...-analyze-atomic-swap-invalidation. The
end-to-end reopen-on-swap path is exercised by the reader-during-rebuild
integration test in a later step.

* fix(analyze): publish a full rebuild via an atomic swap (POSIX)

The full-rebuild path wiped the live index (wipeLbugDbFiles(lbugPath)) and
rebuilt it in place, so a concurrent MCP reader that opened mid-build could
see an empty/half-loaded DB, and a crash between the wipe and the end-of-run
left the index destroyed (recoverable only by --force).

Build the fresh index at <lbugPath>.new and swap it over the live index in one
atomic rename at the end. All DB work flows through the singleton connection,
so only initLbug/wipeLbugDbFiles take the temp target; the close already
checkpoint-consolidates the build to a single file (verified: no residual
.wal/.shadow), so the rename publishes a complete index in one step. A reader
opening mid-build only ever sees the previous complete index; a reader holding
the old inode keeps a consistent stale snapshot until the pool re-opens onto
the new one (the pool staleness invalidation from the prior commit). On
failure the swap is skipped, leaving the previous index byte-for-byte intact.

POSIX only: the common CLI/serve-worker analyze paths skip the native close
(closeLbugBeforeExit, #2264) and leave the build handle open at swap time.
POSIX renames an open file cleanly; a same-process open handle blocks the
rename on Windows, so Windows keeps the current in-place behavior
(buildPath === lbugPath) until that is resolved. The Windows atomic swap and a
deterministic concurrent reader-during-rebuild test are deferred follow-ups.

Steps 2b + partial 3 of docs/plans/2026-07-21-...-analyze-atomic-swap-invalidation.
Integration test asserts the no-temp-leak + inode-swap invariants and the
crash-safety guarantee (a load failure leaves the live index untouched).

* test(analyze): end-to-end read-pool reopen after an atomic swap

Adds the deferred reader-during-rebuild / pool-reopen integration test:
analyze v1 -> read pool serves it -> rebuild with a renamed function (atomic
swap) -> the same repoId's initLbug detects the swapped inode and re-opens the
pool onto the new index. Asserts the pool sees the renamed function and NOT the
stale v1 name, exercising #1 (invalidation) and #2 (swap) together end to end.

* fix(lbug): bound pooled read queries with setQueryTimeout

The read pool relied only on a JS-side Promise.race (QUERY_TIMEOUT_MS) that
frees the waiter but leaves the native call running. Set the engine-level
setQueryTimeout on every pooled connection so a pathological query is bounded
at the source too.

* fix(lbug): name the held-open cause for WAL checkpoint failures (#2599)

A WAL-checkpoint IO error that also carries a busy/lock signal means another
handle (a gitnexus mcp server, or this process's own reader) holds the store
open, not a disk fault. Add isLbugCheckpointBusyError (reusing the tested
isDbBusyError keyword set) and, when the checkpoint driver exhausts its retry
budget on such an error, annotate the surfaced error with the actionable
held-open cause instead of a raw IO string.

Note: overlaps in-flight work on repro/issue-2599-windows-wal-checkpoint;
bundled here at the maintainer's request.

* feat(analyze): opt-in atomic incremental + best-effort Windows swap

Extends the atomic-swap publish (POSIX full rebuild) to two more cases:

- Windows: the swap now applies when a real close is safe to release the build
  handle before the rename — i.e. non-pdg runs (windowsSwapOk excludes --pdg,
  the #2264 destructor-crash case), forcing a real close on the swap path.
  UNVERIFIED on Windows (no Windows runner here); --pdg and any failure fall
  back to today's in-place behavior, so it can never corrupt.

- Incremental (opt-in, GITNEXUS_ATOMIC_INCREMENTAL=1): copies the live index
  into the temp, applies the incremental delete/writeback to the copy, and
  swaps at the end. Off by default because the whole-file copy negates
  incremental's speed premise — kept behind a flag pending a benchmark. The
  escalation valve also targets the temp so an escalated write stays atomic.

Integration test covers the opt-in incremental path end to end (no temp leak,
the incremental change is reflected after the swap).

* refactor(lbug): centralize the read-pool + bridge open-retry budgets

The lbug-config retry registry documented the open/handle-release/query-time
budgets but the read pool's LOCK_RETRY_* (pool-adapter) and the bridge's
LBUG_OPEN_RETRY_* (group/bridge-db) kept private copies that could drift. Move
both into the registry as exported constants (POOL_OPEN_LOCK_RETRY_*,
BRIDGE_OPEN_RETRY_*) and alias the local names to them — one tuning surface,
no behavior change.

* fix: address CI regressions from the bundled follow-ups

- setQueryTimeout: guard the call so test doubles that don't model the engine
  method don't break connection creation.
- atomic swap: skip the rename when the build produced no DB at buildPath (an
  empty repo / mocked pipeline) instead of throwing ENOENT.
- #2599: don't wrap the checkpoint error in the driver (it hid the IO signature
  the CLI's --wal-checkpoint-threshold hint keys on); name the held-open cause
  at the CLI instead, beside that hint, keeping the original error intact.
- retry consolidation: revert to documentation-only — moving the pool/bridge
  budgets into lbug-config broke every explicit lbug-config test mock. The
  registry now catalogues all budgets with their in-file locations.
- analyze-wal-checkpoint-failure test: block both lbug.wal.checkpoint and
  lbug.new.wal.checkpoint, since a full rebuild now checkpoints the temp.

* fix(analyze): publish the swap before stamping meta; identity-gate the reader (#2614 F1)

Review found a HIGH regression: the full-rebuild wrote the freshness stamp
(saveMeta, indexedAt=T_new) BEFORE the atomic swap, so a concurrent MCP reader
that reinited in the saveMeta->swap window opened the OLD inode, recorded
observed=T_new, and then never reinited again (ensureInitialized returns early
on 'current') — serving the pre-rebuild graph indefinitely. The build-into-temp
change inverted the pre-PR invariant that 'meta shows T_new' implied 'lbugPath
holds T_new data'.

Two coordinated fixes:
- run-analyze: move the final saveMeta AFTER the swap, so meta.indexedAt only
  becomes visible once lbugPath resolves to the new inode. Verified nothing in
  the span reads on-disk meta and registerRepo writes only the registry.
  Leaving the dirty flag set across the swap also improves crash-safety.
- local-backend: the reader staleness gate now also compares the lbug file
  IDENTITY (ino/mtime/size), reiniting on an inode change even when
  meta.indexedAt is unchanged. This closes the swap-window latch and covers the
  in-place incremental case — and is what actually makes the pool's dbIdentity
  net reachable for the MCP reader (the indexedAt gate otherwise bypassed it).

* fix(lbug/analyze): WAL-aware incremental, residual-sidecar reconcile, Windows opt-in, #2599 anchor (#2614 F2-F4)

Review remediations:
- F3: gate atomic incremental on a CLEAN live index (inspectLbugSidecars) — the
  main-file-only copy would drop an orphan .wal's delta; fall back to in-place.
- F4: on the swap, MOVE a residual <buildPath>.wal/.shadow beside the published
  index (not orphan it) so a swallowed final checkpoint's delta is replayed.
- F2: record identity on the shared read-only Database and warn when a cached
  handle is reused after its on-disk index was rebuilt while another consumer
  holds it (unreachable via MCP — one consumer per lbugPath; a complete fix
  needs per-inode handles, documented).
- Windows swap: opt-in (GITNEXUS_ATOMIC_WINDOWS_SWAP=1), default off — the
  forced real close re-bets an unproven #2264 assumption and can't be verified
  without a Windows runner, so the default Windows path stays in-place.
- #2599: anchor isLbugCheckpointBusyError to real held-open wording instead of
  isDbBusyError's bare .includes('lock') over a message that embeds the DB path
  (a repo under blockchain-app misclassified a disk fault as held-open).
- Docs: corrected retry-catalogue budgets (linear, not exp) and the
  checkedOut>0 bound comment (load-bounded, not IDLE_TIMEOUT_MS).

* test(analyze): cover the production close path in the atomic swap (#2614 F5)

Adds a full-rebuild swap test with skipNativeCloseOnExit:true — the close path
the CLI and serve-worker actually ship (build handle left open at swap time),
distinct from the default real-close the other swap tests exercise. Asserts the
POSIX swap still publishes a single consolidated lbug with no .new temp and no
orphan sidecar.

* test(analyze): give the follow-up git commits an inline identity (CI fix)

The end-to-end reopen and atomic-incremental tests' second commits used a bare
`git commit`, which fails on CI runners with no global git identity (empty
ident name). makeRepo's initial commit already passes -c user.name/-c
user.email inline; apply the same to the rename/change commits. No code change.

* fix(mcp): route reader reinit through initLbug's active-query guard (#2614 review)

Review found an active-query retirement race: LocalBackend.ensureInitialized
detected an identity/stamp change and called closeLbug(poolKey) DIRECTLY, but
closeOne closes the shared Database at refCount 0 regardless of checked-out
connections. So a reader detecting the new generation could close the Database
a concurrent query is still executing on — a native use-after-free. This
bypassed the checkedOut>0 guard that initLbug itself has.

Fix (delegate, not close directly): initLbug now returns whether it actually
rolled the pool over; ensureInitialized calls initLbug (which serves the
current handle while a query is in flight and reopens only when idle) instead
of closeLbug. The observed IDENTITY is advanced only when the pool actually
reopened — if a query was in flight, the identity stays divergent and the
reopen retries on a later idle check rather than latching on the old handle.
The observed STAMP advances regardless so a same-file stamp change can't loop.

Old generation now stays alive until its in-flight queries drain (lazy
rollover); new requests during the busy window share the old handle until the
pool goes idle, then reopen. No parallel open-both-generations, but no UAF and
no stale latch.

---------

Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
2026-07-21 21:58:00 +01:00
Gergo Magyar
382801790c perf(config): memoize core.excludesFile / info/exclude resolution (#2606)
loadIgnoreRules is called once per repo, per language/contract
extractor during group sync -- an N-repo group fans out to 6+
extractors each calling it, turning an uncached execSync per call into
O(extractors x repos) blocking subprocess spawns for the exact
many-repos scenario #2606 describes.

Both getGitInfoExcludePath and getCoreExcludesFilePath resolve to the
same value for the same fromPath for the life of the process, so
memoize by fromPath in a process-lifetime Map. One-shot CLI runs are
unaffected by staleness; the long-lived MCP server would need explicit
invalidation if this becomes a real concern.
2026-07-21 19:09:50 +00:00
Gergo Magyar
0f016dc467 fix(config): read core.excludesFile and .git/info/exclude for global ignores (#2606)
Replace the custom ~/.gitnexus/ignore file with the same two sources
real git itself consults for exactly this purpose (gitignore(5)):

- core.excludesFile: git's own all-repos global ignore file (defaults
  to $XDG_CONFIG_HOME/git/ignore when unconfigured)
- $GIT_COMMON_DIR/info/exclude: per-repo, untracked, so it works
  without push/commit access to the repo

Precedence mirrors git exactly (lowest to highest): core.excludesFile,
then info/exclude, then .gitignore, then .gitnexusignore -- each later
source can negate an earlier one via a `!pattern` line, same
last-match-wins semantics git itself uses.

Adds getCoreExcludesFilePath and getGitInfoExcludePath to git.ts,
following the same execSync + git-common-dir pattern as
getCanonicalRepoRoot. GITNEXUS_NO_GLOBAL_IGNORE (or noGlobalIgnore)
still skips both global sources, mirroring GITNEXUS_NO_GITIGNORE.
2026-07-21 18:06:22 +00:00
Gergo Magyar
a4a79ac920 style: fix prettier formatting in ignore-service.test.ts 2026-07-21 17:41:18 +00:00
Gergő Magyar
8a67acb9dd
Merge branch 'main' into fix/2606-global-ignore-file 2026-07-21 18:39:22 +01:00
Gergo Magyar
322e05a6be fix(config): add user-level global ignore file (#2606)
IgnoreService only read per-repo .gitignore/.gitnexusignore, so an
exclusion meant to apply across every indexed repo had to be repeated
per repo or hand-patched into node_modules (wiped on every upgrade).

loadIgnoreRules now also reads a global ignore file at
$GITNEXUS_HOME/ignore (default ~/.gitnexus/ignore), reusing the
existing global directory that already holds registry.json and
config.json. It is added first, so per-repo .gitignore/.gitnexusignore
rules can still negate it, mirroring the .gitignore -> .gitnexusignore
precedence already in place. GITNEXUS_NO_GLOBAL_IGNORE (or
noGlobalIgnore) skips it, mirroring GITNEXUS_NO_GITIGNORE.
2026-07-21 16:58:07 +00:00
Gergő Magyar
aa8a441202
Merge branch 'main' into fix/2605-rename-edit-count 2026-07-21 17:34:36 +01:00
Gergo Magyar
9f57984372 style: fix quote style per prettier in new dyn-normalization test 2026-07-21 16:12:20 +00:00
Gergo Magyar
e18b4416c6 test(rust): cover Box<dyn Trait> and dyn-bound-list normalization (#2604)
GitNexus review-agent finding: stripDynBound's documented Box<dyn Trait>,
Rc/Arc<dyn Trait>, and auto-trait/lifetime bound-list (dyn Trait + Send)
shapes had no test anywhere — only the bare &dyn Trait parameter case was
exercised end-to-end. Add direct unit coverage on normalizeRustTypeName and
(via interpretRustTypeBinding) normalizeRustReturnType for these shapes.
2026-07-21 16:10:01 +00:00
Gergo Magyar
a7bfe819eb test: update hardcoded schema-version expectations for v11 (#2604)
call-summary-schema-version.test.ts pins INCREMENTAL_SCHEMA_VERSION as a
literal per bump, documenting the reuse-gate boundary for each version.
Update the "current" expectation to 11 and add the v10 pre-current case,
matching the v7/v8/v9/v10 precedent already in the file.
2026-07-21 15:42:58 +00:00
Gergo Magyar
66f11badaf style: wrap long filter predicate per prettier (PR autofix) 2026-07-21 15:19:48 +00:00
Gergo Magyar
54c44d91de fix(mcp): reconcile rename report on partial failure; harden enumerate (#2605)
Addresses gitnexus-review-agent findings on PR #2608:

- MED: on a partial apply (a file's write throws), drop that file's edits
  from total_edits/graph_edits/text_search_edits/changes so the reported
  result describes what actually reached disk, not what was attempted. The
  comprehensive enumeration otherwise let a failing file contribute its
  entire line count as phantom 'applied' edits. failed_files still names
  every dropped file. Counts are now derived once from the reported set.
- MED: hoist the word-boundary regexes out of the per-line loop (one compile
  each instead of one per line), reused by the apply loop.
- LOW: apply loop reuses escapedOldName instead of recomputing the escape
  formula inline (removes a preview/apply drift risk).
- Soften the in-code comment: enumeration gives per-call preview/apply
  consistency; the pre-existing two-read TOCTOU (external write between
  preview and apply) is out of scope and noted, not newly introduced.

Tests: add a mixed graph-ref + text_search multi-file case (asserts per-file
confidence and the never-downgrade guard, via a stubbed rg), and a
partial-write-failure case (asserts only landed files are reported). Assert
concrete graph_edits/text_search_edits splits, not just their sum.
2026-07-21 15:14:32 +00:00
Gergő Magyar
aaefbda226
Merge branch 'main' into fix/2604-rust-trait-object-dispatch 2026-07-21 16:12:24 +01:00
Gergo Magyar
881c6bccc7 test(rust): regenerate captures golden snapshot for function_signature_item
Expected drift from the query.ts change: abstract trait methods now emit a
scope + declaration capture, shifting captureGroups/digest for every rust-*
fixture containing a trait with a required (bodyless) method.
2026-07-21 14:49:45 +00:00
Gergo Magyar
052319c9cc test(rust): add regression coverage for trait-object dispatch (#2604)
New minimal fixture (single trait + impl + &dyn Trait call site, no other
same-named callers) proves the dyn-dispatch CALLS edge discriminates: fails
against the pre-fix source (0 edges) and passes against the two preceding
commits' fix (exactly 1 edge, verified via the CLI analyze pipeline against
a standalone repo).

The existing rust-abstract-dispatch fixture was NOT extended for this,
deliberately: it already has other callers referencing the same method
names (process()'s repo.find()/save()/count()), and an existing resolution
fallback picks those up via simple-name matching regardless of receiver
type — masking this specific defect in the in-process test-pipeline path.
A dedicated, single-caller fixture keeps the regression test load-bearing.
2026-07-21 14:48:54 +00:00
Gergő Magyar
4dd16ea8c9
Merge branch 'main' into fix/2605-rename-edit-count 2026-07-21 15:43:53 +01:00
Gergo Magyar
902186c4f8 style: prettier-format rename-edit-report test (#2605) 2026-07-21 14:41:19 +00:00
Gergo Magyar
4e97a278d1 fix(mcp): report every rename edit that apply writes (#2605)
rename() reported total_edits from a partial enumeration (definition line
only, one-edit-per-graph-file then break, and text search that skipped any
file already covered by the graph) while the apply step does a whole-file
\boldName\b global replace on every touched file. When a private symbol's
definition and all its call sites live in one file, only the definition line
was reported (total_edits: 1) even though apply rewrote every occurrence, in
both dry-run and apply.

Rebuild changes/total_edits/graph_edits/text_search_edits from one file set:
classify each file to rewrite (definition + graph refs = graph confidence;
rg-only files = text_search, never downgrading a graph file), then enumerate
every matching line per file with apply's exact escaped global regex. The
reported edit list now equals what apply writes. Apply behavior is unchanged.

Adds a regression test reproducing the issue's single-file Rust case (def +
3 same-file call sites, empty graph): total_edits is 4 in both dry-run and
apply, and equals the replacements that land on disk.
2026-07-21 14:31:47 +00:00
Claude
70e0a7766c fix(java): address #2561 review — inherited-dispatch test + bodied fail-safe
Two gitnexus-review-agent findings on PR #2602:

- MEDIUM: the bodied-constant MRO-to-host-enum path (a qualified call to an
  inherited, non-overridden enum method) was claimed in a comment but never
  tested. Add EnumConst.A.log() -> EnumConst.log#0, exercising E$N's
  @reference.inherits MRO arm end to end.

- LOW: `bodiedName ?? hostEnum` conflated "body-less" with "name synthesis
  failed on a bodied constant" (reachable only on malformed/error-recovery
  trees), silently binding an overriding constant's receiver to the host
  enum — a wrong edge instead of no edge. Switch to `isBodied ? bodiedName :
  hostEnum` so a bodied constant binds ONLY to its E$N class, mirroring the
  object_creation_expression branch's skip-on-synthesis-failure. Verified
  output-neutral on the well-formed bench corpus.

Rebaseline the java scope-capture fingerprint (a822cef9 -> d04298a9): the
bench corpus IS test/fixtures/lang-resolution, so the new dispatchInherited
fixture method shifts it (+6 capture groups); the logic change contributes
nothing (confirmed by isolating the fixture-only fingerprint). java.test.ts
242 passed; measure.mjs --check PASS (14 languages); tsc/prettier/eslint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 13:52:27 +00:00
Gergő Magyar
ced02df06f
Merge branch 'main' into fix/2561-enum-constant-receiver-dispatch 2026-07-21 14:40:37 +01:00
Ko
2a85425ad8
Merge pull request #2542 from GenKoKo/fix/worker-stdout-and-ready-timeout
fix(ingestion): pipe worker stdout and make ready timeout configurable
2026-07-21 13:54:34 +01:00
Gergő Magyar
c111dfd4ae
Merge branch 'main' into fix/2561-enum-constant-receiver-dispatch 2026-07-21 12:50:37 +01:00
Claude
7666a009f0 fix(java): resolve E.CONST.method() enum-constant receiver dispatch (#2561)
Calling a method on an enum-constant receiver (E.CONST.method()) emitted
no CALLS edge. The receiver "E.CONST" is a two-segment compound receiver;
resolveCompoundReceiverClass walks each dotted segment via the owning
class scope's typeBindings map, but enum constants had no typeBinding, so
the constant segment dead-ended and no target was ever resolved.

#2555/#2558 gave bodied constants a first-class synthesized E$N class with
an MRO that includes the host enum; this is the receiver-side follow-up.
synthesizeJavaAnonymousClassDeclarations now emits a class-scope
typeBinding for every enum constant's simple name -> its E$N class (bodied)
or the host enum itself (body-less), reusing the exact mechanism a field
declaration uses. The generic compound-receiver chain walk then resolves
E.CONST.method() with no change to any shared scope-resolution code.

Bodied dispatch (EnumConst.A.hook() -> EnumConst$1.hook#0) and body-less
inherited dispatch (Plain.A.m() -> Plain.m#0) are covered by new tests in
the existing java-enum-constant-body fixture; both were verified to fail
against the pre-fix tree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 11:24:35 +00:00
Claude
1415bd5c2f docs(embeddings): update engines-floor comments for the 22.18 minimum
The module.registerHooks compat seam and the onnxruntime resolvers cited
the old '>=22.0.0' floor as the reason their sub-22.15 fallback was
reachable. With the floor now ^22.18.0 || >=24.11.0 (all >=22.15), every
supported runtime exposes the API; the fallback stays as defensive
handling for below-floor runtimes (engines is advisory, not
engine-strict). Comments only - no behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 10:09:34 +00:00
Claude
eea9ac92dc ci: move Node pins to the 22.18 floor
With the supported minimum raised to Node 22.18, retarget every lane and
pinned runtime that sat at a lower version so nothing builds or runs the
package on an unsupported (EBADENGINE-warning) Node:

- ci-tests.yml: node-floor-compat 22.14 -> 22.18.0 (name, comment, pin,
  version assertion) so the floor gate guards the new minimum; its #2372
  registerHooks failure mode cannot recur above 22.15. Containment-canary
  pin 22.16.0 -> 22.18.0.
- gitnexus-review-agent.yml + the pinned review/canary runtime: the
  reproducible runtime is version-locked in lockstep across
  .github/{gitnexus-review-runtime,claude-canary-runtime}/package.json and
  their lockfiles (engines), the workflow's node-version, its two
  'node --version = v22.18.0' assertions, the lockfile-engines guard, and
  NODE_VERSION. Moved all of them 22.16.0 -> 22.18.0.
- gitnexus-skill-evolution.yml: pinned runtime 22.16.0 -> 22.18.0.
- CONTRIBUTING.md prerequisite floor updated.
- review-agent-workflow.test.ts, which enforces the runtime lock, updated
  to expect 22.18.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 10:09:34 +00:00
Gergő Magyar
a2cce72fa0
Merge branch 'main' into dependabot/npm_and_yarn/gitnexus/ladybugdb/core-0.18.2 2026-07-21 10:18:38 +01:00
Gergő Magyar
c7701613ec
Merge branch 'main' into dependabot/npm_and_yarn/gitnexus/ladybugdb/core-0.18.2 2026-07-21 09:52:37 +01:00
Gergő Magyar
450f641b36
Merge branch 'main' into codex/spring-config-bindings-2412 2026-07-21 09:51:42 +01:00
Gergő Magyar
522d1ee62a
Merge branch 'main' into fix/2564-record-newexpr-callgraph 2026-07-21 09:29:45 +01:00
Gergo Magyar
dac2b770a0 fix(test): address gitnexus-review-agent findings on PR #2598
- Anchor isBenignDropFtsIndexError to the START of the message
  (startsWith, not includes) so a future genuine failure that merely
  mentions "Binder exception" or "Catalog exception" mid-message can't
  be misclassified as benign. New test proves the old substring match
  would have swallowed such a message.
- incremental-fts-drop-ordering.test.ts: probe FTS availability once in
  beforeAll and skip VISIBLY via ctx.skip() in beforeEach (matching the
  withTestLbugDB/lbug-vector-extension convention) instead of a silent
  console.warn+return inside the test body, which reported a false pass
  with zero coverage of the ordering invariant when FTS was unavailable.
  The post-first-run FTS-index-built check is now a hard assertion
  instead of a second soft skip, since the beforeEach gate already
  proved the extension loads.
2026-07-21 07:57:28 +00:00
Gergo Magyar
f460157470 style(test): fix prettier formatting in incremental-fts-drop-ordering.test.ts
CI's prettier --check flagged one over-long line; no behavior change.
2026-07-21 07:25:15 +00:00
Gergo Magyar
d7a4f47580 fix(analyze): drop FTS indexes before the incremental DETACH DELETE
Fixes #2589: incremental analyze intermittently crashed with "FTS
index 'file_fts' is inconsistent: term is missing during delete" after
markdown-only commits, and --repair-fts also failed in that state.

deleteNodesForFiles' batched DETACH DELETE ran against tables that
still carried the FTS index built at the end of the PREVIOUS analyze
run -- createSearchFTSIndexes only drops+rebuilds every index in Phase
3, well after that delete already ran. LadybugDB's FTS extension is
not proven to survive DML against an indexed table (its own docs never
demonstrate the sequence). Call the new dropSearchFTSIndexes() up front
in the non-escalated incremental branch, before deleteNodesForFiles --
Phase 3 still rebuilds every index from the final row set regardless.

New end-to-end test drives a real runFullAnalysis full+incremental
cycle and confirms it fails without this change (file_fts and 50
sibling indexes still present at delete time) and passes with it.
2026-07-21 07:04:50 +00:00
Gergo Magyar
c548652eca fix(lbug): stop dropFTSIndex from swallowing genuine engine failures
dropFTSIndex previously caught and discarded every DROP_FTS_INDEX error
unconditionally. Extract isBenignDropFtsIndexError, a pure classifier
for the two legitimate "nothing to drop" cases (Binder/Catalog
exceptions: index never created, or the FTS function isn't registered)
verified end-to-end against @ladybugdb/core 0.18.x's real conn.query()
error text. Anything else -- e.g. the Runtime exception "FTS index is
inconsistent" class from #2589 -- now rethrows instead of being masked,
so a corrupted index can no longer persist across analyze runs
undetected.
2026-07-21 07:04:50 +00:00
Gergo Magyar
1595a90a13 fix(storage): bump INCREMENTAL_SCHEMA_VERSION for the Java record fix (#2564)
Review finding: the record_declaration container-node fix (894110bf)
makes previously-uncaptured Record nodes and HAS_METHOD edges appear
for the first time, but the incremental write set only covers changed
files. Without this bump, an existing index would silently keep
omitting the Record node and its HAS_METHOD edges for unchanged
record files after an ordinary incremental analyze.

Same contract as v7 (#2437/#2522) and the two closest precedents, v8
(#2550) and v9 (#2555), which bumped this constant for the identical
"model X as first-class node" class of change.
2026-07-21 07:04:08 +00:00
Gergo Magyar
0b933aa43f fix(java): treat a new-expression as a typed receiver for its chained call (#2564)
new Local().inner() bound the whole object_creation_expression as
@reference.receiver, so its raw source text ("new Local()") became the
receiver name. That text can never match a scope binding, so the call
silently fell through to name-only fallback resolution and could
resolve to an unrelated same-named method on a collision.

Normalize the receiver to the constructed type's simple name (reusing
javaBaseSimpleNameOf, already used for the anonymous-class inheritance
edge) so Case 2 (class-name / static receiver) in
receiver-bound-calls.ts resolves it via its normal MRO walk. Mirrors
the existing normalizePhpReceiver precedent in php/captures.ts - a
language-local capture rewrite, no shared-pipeline change.
2026-07-21 07:04:07 +00:00
Gergo Magyar
1e190e6fdd fix(java): emit a graph node for record_declaration (#2564)
JAVA_QUERIES had no @definition.record capture, unlike its
class_declaration/interface_declaration/enum_declaration siblings and
unlike CSHARP_QUERIES' own record_declaration pattern. A Java record's
container node was never created, so its HAS_METHOD edges were dropped
at persistence even though ownership resolution computed a valid
ownerId for its methods.

Downstream label mapping, the class-extractor config, the dispatch
table, and ownership reconciliation already treated 'Record' correctly
- this was purely a missing structure-phase capture.
2026-07-21 07:04:07 +00:00
Gergo Magyar
a638867400 Merge branch 'dependabot/npm_and_yarn/gitnexus/ladybugdb/core-0.18.2' of origin into local 2026-07-21 06:16:48 +00:00
Gergo Magyar
30ec68fa7a fix(test): harden findInstalledFtsExtension for cross-OS filesystem quirks
Wrap the version-directory scan in try/catch so a transient FS error
(permission denial, an AV file lock on Windows, a directory vanishing
mid-scan) fails closed to null instead of throwing — matching the
original callers' contract, and safer across the Windows/macOS/Linux
CI matrix where these error modes differ.

Also drop the redundant USERPROFILE/HOME manual chain in
extension-binary-real.test.ts in favor of the repo's established
os.homedir() convention (already used ~15 other places here), which
Node resolves correctly per-OS and already honors env overrides.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 06:14:58 +00:00
Gergő Magyar
cdeb9b59a5
Merge branch 'main' into dependabot/npm_and_yarn/gitnexus/ladybugdb/core-0.18.2 2026-07-21 07:10:13 +01:00
Gergo Magyar
b751418985 fix(test): discover the installed FTS extension version dir instead of assuming it equals lbug.VERSION
resolveInstalledFtsExtension (extension-binary-real.test.ts) and
resolveSeedExtension (fts-extension-e2e.test.ts) both hardcoded the
on-disk FTS extension path as .lbdb/extension/<lbug.VERSION>/..., but
LadybugDB's native INSTALL/LOAD resolves its own extension-ABI version
directory, which does not always track the npm package version. Bumping
@ladybugdb/core from 0.18.1 to 0.18.2 in this PR still installs into a
0.18.1 directory, so both hardcoded lookups came up empty and failed
hard under GITNEXUS_REQUIRE_FTS=1 in CI (all platforms, shard 3).

Add findInstalledFtsExtension() to discover the real installed file by
scanning every version subdirectory, and use it from both test files.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 06:06:42 +00:00
Shining
41e590fed7 fix(spring): harden configuration bindings 2026-07-21 13:44:43 +08:00
Gergő Magyar
ce6cefe696
Merge branch 'main' into codex/spring-config-bindings-2412 2026-07-21 06:19:19 +01:00
ChunxueLi
84b4402cd1
fix: remove hardcoded 300-flows cap for large repositories (#2198)
* fix: remove hardcoded 300-flows cap for large repositories

The dynamicMaxProcesses was capped at 300 via Math.min(300, ...),
causing large repositories (280K+ nodes) to lose execution flows.

Change: Remove the Math.min(300, ...) cap, keep dynamic calculation.
Effect: 280K-node repo: 300 → 1617 flows.

* test: add regression for dynamic maxProcesses sizing (#2198)

Verify that processProcesses honours maxProcesses > 300 without truncation.
Addresses the optional follow-up suggested by @koriyoshi2041.

* test: exercise computeDynamicMaxProcesses at the phase layer (#2198)

Extract  from the inline
expression in  so the regression test can exercise the
function that actually contained the removed  cap.

The previous test called  directly with
, which passes regardless of whether the phase-level
cap is present —  never had the cap.

The new test suite covers:
  - floor (20) for tiny repos
  - linear scaling in the 0–3000 range
  - growth past 300 for large repos (the actual regression)
  - explicit assertion that reintroducing Math.min(300, …) would fail

Addresses review feedback from @azizur100389.

---------

Co-authored-by: Ubuntu <ubuntu@localhost.localdomain>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
2026-07-21 05:48:55 +01:00
ChamHerry
7534f53c27
feat(embeddings): control request-body dimensions via GITNEXUS_EMBEDDING_REQUEST_DIMS (#2574)
* feat(embeddings): support GITNEXUS_EMBEDDING_REQUEST_DIMS=omit

What: Honor GITNEXUS_EMBEDDING_REQUEST_DIMS=omit by suppressing the request-body
`dimensions` field sent to HTTP embedding backends.

Why: Strict OpenAI-compatible backends return vectors in the model's native size
but reject an unfamiliar `dimensions` field, breaking `analyze --embeddings`
against them. The var was parsed but never propagated, so `omit` was a no-op.

How: Add `requestDimensions` to HttpConfig, return it from readConfig, and forward
`config.requestDimensions` (not the validation-only `config.dimensions`) to
`httpEmbedBatch`. Local dimension checks still use `config.dimensions`.

Details: Coexists with the retry/pacing fields introduced upstream; both feature
sets are preserved. Default behavior unchanged when REQUEST_DIMS is unset.

Impact: gitnexus/src/core/embeddings/http-client.ts; README; unit tests.

* fix(embeddings): name GITNEXUS_EMBEDDING_REQUEST_DIMS in its own config error

Address the review findings on #2574.

What:
- A malformed GITNEXUS_EMBEDDING_REQUEST_DIMS now throws an error naming
  GITNEXUS_EMBEDDING_REQUEST_DIMS, not the sibling GITNEXUS_EMBEDDING_DIMS.
- isHttpEmbeddingDimsError recognizes both leads, so the CLI still classifies
  the REQUEST_DIMS config mistake as a clean config error, not a stack dump.
- Tests: numeric-override decoupling (DIMS=1024 validates the response while
  REQUEST_DIMS=512 is sent in the body), the omit aliases (none/off/false/0),
  and the malformed-value error path (which also pins the naming fix).
- README documents the full accepted values: omit-aliases and integer override.

Why: readConfig reused the DIMS error lead for the REQUEST_DIMS branch, so
REQUEST_DIMS=garbage misdirected the operator to edit the wrong variable. The
feature's actual decoupling and its non-omit inputs had no test coverage.

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

---------

Co-authored-by: wangxc <wangxc_a_bj@si-tech.com.cn>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 05:46:20 +01:00
Shining
9096f6924c feat(spring): bind configuration consumers 2026-07-21 10:07:20 +08:00
Gergő Magyar
8fd1f8a8d8
test(skills-e2e): give the Idempotency setup hook the 120s budget its siblings use (#2583)
The Idempotency beforeAll runs runSkillsCli (analyze --skills) twice, each
capped at 45s, under a 90s hook budget — exactly 2x the per-call timeout,
with no headroom for fixture creation and git init. On slow Windows CI
runners the two analyzes plus setup exceed 90s and the hook times out
('Hook timed out in 90000ms'), failing the shard before the test's own
status===null timeout tolerance can apply. Every other describe hook in
this file already uses 120s; align this one.

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 19:54:54 +01:00
Claude
05dadb7950 fix(lbug): raise the adaptive pool floor to a COPY-safe 256 MiB
The 64 MiB floor was too small: LadybugDB's bulk COPY needs working
buffer-pool memory that scales with the repo, so a 64 MiB pool fails with
"buffer pool is full and no memory could be freed" on any non-trivial
repo (empirically: the 6-file skills-e2e idempotency fixture needs
>=128 MiB; the ~1800-file GitNexus checkout needs >=256 MiB). Introduce a
distinct ADAPTIVE_POOL_FLOOR (256 MiB) for the hint clamp, kept separate
from BUFFER_POOL_FLOOR (64 MiB), which still guards defaultBufferPoolSize
on tiny-RAM machines; the hint is still clamped up to the machine default
so it can never over-commit.

This keeps the change as what it actually is — a large-repo optimization:
GitNexus full analyze is 51s (2 GiB) -> 35s (adaptive ~414 MiB). Small
repos now open COPY-safely at 256 MiB instead of the 2 GiB default (same
wall time on Linux, where commit is lazy; the eager commit is far cheaper
than 2 GiB on Windows). The reframed comments drop the earlier
unrepresentative "3-file repo / 64 MiB fast" claim.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 16:29:17 +00:00
Claude
ad5ff42804 feat(lbug): add adaptive buffer-pool size hint
Adds the sizing lever without changing behavior yet: a module-scoped
buffer-pool size hint plus estimateBufferPool(graphElementCount), read by
resolveBufferManagerSize with precedence env-override > clamp(hint, 64MiB,
default) > default. The hint can only shrink the pool from the default
(clamped to [floor, default]), so the 2GiB/80%-RAM cap and the
GITNEXUS_LBUG_BUFFER_POOL_SIZE escape hatch (incl. 0) are preserved. With
no hint set, resolveBufferManagerSize returns exactly what it did before.

Motivation: LadybugDB eagerly commits the buffer pool at DB open, so the
fixed min(2GiB,80%RAM) pool adds a measured ~2.8s to every analyze even on
a 3-file repo (dominant on Windows). Sizing the pool to the graph lets
small repos use the fast 64MiB floor while large repos keep the cap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 16:29:17 +00:00
Abhigyan Patwari
2ea00a2b22
fix(ci): install root and shared node_modules for the evolution benchmark (#2575)
* fix(ci): install root and shared node_modules for the evolution benchmark

The first real workflow_dispatch of the skill-evolution loop failed at task
binding: capture_task_dependency_binding aborted with

  SandboxError: sandbox_copy path is unavailable: node_modules: No such
  file or directory

The benchmark tasks sandbox-copy node_modules from three locations
(tasks.scenarios.yaml) — the monorepo root, gitnexus-shared, and gitnexus —
mirroring a full dev checkout. The install step only ran `npm ci` in
gitnexus/, so the root and gitnexus-shared node_modules never existed and
the loop died before any agent ran. Install all three (root, then build
gitnexus-shared, then build gitnexus), matching the per-package install in
ci-tests.yml plus the root deps the tasks require.

A new contract test pins all three installs so this fails in CI rather than
on the next real run — the same guard the workflow's other two P1 fixes got.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Va5uu9Ar3e45QZ5xFsG4AZ

* fix(ci): only add the missing root install; the subpackage steps already exist

The initial fix redundantly rebuilt gitnexus-shared and gitnexus inside the
gitnexus step — but the workflow already builds both in their own dedicated
steps. Only the monorepo root node_modules was missing. Add a single
"Install monorepo root dependencies" step and leave the two subpackage
build steps untouched, so the benchmark's root sandbox_copy resolves without
double-building.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Va5uu9Ar3e45QZ5xFsG4AZ

---------

Co-authored-by: Gergo Magyar <gergomagyar@icloud.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 09:56:54 +01:00
FAll
2cfbc4a259
feat(spring): build bean candidate inventory (#2494)
* feat(java): inventory Spring bean candidates

* fix(java): fail closed on Spring annotation shadowing

* fix(java): resolve Spring beans after imports

* fix(java): remove stale bean extraction path

* style: satisfy locked Prettier version

* fix(spring): address PR review findings

* feat(spring): share bean inventory across Java and Kotlin

* fix(spring): gate bean inventory analysis completeness

* fix(kotlin): avoid reloading cached scope source

* chore(autofix): apply prettier + eslint fixes via /autofix command

---------

Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-20 09:28:23 +01:00