Commit graph

1078 commits

Author SHA1 Message Date
henry201605
39a0c677df
Merge branch 'main' into fix/fts-non-fatal-in-analyze 2026-05-25 19:10:00 +08:00
Gergő Magyar
5e8690f992
feat(progress): add per-language progress reporting to scope-resolution phase (#1813)
* feat(progress): add per-language progress reporting to scope-resolution phase (#1741)

The scope-resolution phase (which can run 74+ minutes on large Java/Kotlin
repos) previously emitted zero progress updates, causing the CLI progress bar
to freeze at ~49% with a stale "Parsing code" label — making users think
the tool was stuck.

- Add `scopeResolution` to PipelinePhase type and PHASE_LABELS
- Add `onProgress` callback to `runScopeResolution` with per-file updates
  during the extract loop and sub-phase boundary markers (building scope
  model, resolving references, emitting edges)
- Wire progress through `scopeResolutionPhase` with pre-counted file totals,
  per-language labels, and pipeline-wide percent mapping (90-95 internal)
- Bump mro/communities/processes percent ranges to 95-100 to maintain
  monotonic progress after scope resolution
- Add `scopeResolution` to mro's deps (latent ordering fix: mro reads
  EXTENDS edges that scope resolution writes via preEmitInheritanceEdges)

* fix(progress): clamp overallRatio, fire final extract event, fix mro @deps JSDoc

- Clamp overallRatio to [0,1] so percent never exceeds 95 when
  readFileContents drops files (langFileCount < totalScopeFiles)
- Fire onProgress for the last file in the extract loop even when
  files.length is not divisible by progressInterval
- Update mro @deps JSDoc to include scopeResolution

* fix(progress): ensure bar redraws at every state transition

- Fire initial 'extracting' event at file 0 so the sub-phase label
  appears immediately, not after progressInterval files
- Emit a completion event at percent 95 when scope resolution finishes
  so the bar definitively reaches the phase ceiling before mro starts

* feat(progress): improve UX with human-readable elapsed, language counter, cleaner labels

- Format elapsed time as "5m 12s" / "1h 20m" instead of raw "(312s)"
  for all pipeline phases (CLI-wide improvement)
- Add language counter "[1/3]" to scope-resolution detail so users
  know how many languages remain and which is active
- Rename sub-phases for clarity: "building scope model" → "analyzing
  types", "emitting edges" → "linking symbols"
- Remove nested parentheses from detail strings for cleaner display
- Expand scope-resolution percent range from 5 to 8 points (90-98
  internal → 54-59% display) for more visible bar motion
- Re-allocate mro (98), communities (98-99), processes (99-100)

* feat(progress): typed sub-phases, i18n locales, and test coverage

- Extract ScopeResolutionSubPhase union type with exhaustive switch
  guard so adding a sub-phase without updating phase.ts is a compile
  error
- Add scopeResolution key to en and zh-CN locale files so the web UI
  shows translated labels instead of raw message fallback
- Extract formatElapsed to its own module with 7 boundary-value tests
  (0s, 59s, 60s, 3599s, 3600s, 3661s, 7323s)
- Add runScopeResolution onProgress integration test proving sub-phase
  order (extracting → analyzing types → resolving references → linking
  symbols) and the 0-file early-return path

---------

Co-authored-by: Test <test@example.com>
2026-05-25 11:53:54 +01:00
Alaa Kaddour
efcab45560
feat(web): support GITNEXUS_BACKEND_URL env var for Docker deployments (#1286)
* feat(web): support GITNEXUS_BACKEND_URL env var for Docker deployments

* fix(docker): escape inline script injection to prevent XSS and add server-level integration tests

- Add jsonForScriptTag() that escapes <, >, & after JSON.stringify to prevent </script> breakout in inline config script
- Sanitize rawBackendUrl in warning log to prevent log injection via newlines
- Replace 5 duplicated-helper injection tests with 7 server-level HTTP integration tests that spawn the real docker-server.mjs with GITNEXUS_BACKEND_URL set
- Add XSS-specific test: URL containing </script> must produce exactly 1 <script> tag
- Add empty-string backendUrl frontend test
- Improve Docker Compose Linux guidance with explicit <server-ip> example

* fix(docker): harden log sanitization, fix error leak, fix killAndWait race

- Broaden log sanitization regex from [\r\n] to [\x00-\x1f\x7f] to strip
  all C0 control characters including ANSI escape sequences
- Replace error.message leak in 500 handler with generic string; log the
  real error server-side via console.error
- Fix killAndWait TOCTOU race by registering exit listener before kill
  and adding post-kill exitCode guard

* fix(docker): handle readFile race to resolve CodeQL file-system-race alert

Wrap readFile in try/catch so the TOCTOU between stat() and readFile()
is handled gracefully — if the file vanishes between the check and the
read, return 404 instead of crashing.

* @
fix(docker): eliminate TOCTOU race and format web components

Replace the previous try/catch approach with fs.promises.open() to
get a file handle, then use handle.stat()/readFile()/createReadStream()
from the same fd — properly eliminates the CodeQL "file system race
condition" alert by removing the window between stat() and read.

Also runs prettier on the 5 web component files that were failing
the format CI check.
@

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

* chore: trigger CI

* @
fix(docker): pass GITNEXUS_BACKEND_URL to the web container

The env var was documented but commented out, so docker-server.mjs
never received it and the config injection was dead. Uncomment
the environment block with a passthrough default so users can
set GITNEXUS_BACKEND_URL in .env or their shell for remote/custom
deployments.
@

* @
fix(docker): eliminate stat() to resolve CodeQL js/file-system-race

CodeQL pairs any stat() (FileCheck) with a subsequent open() (FileUse)
on an aliased path. The previous approach kept stat() for directory
detection, which the analyzer flagged regardless of the fd-based reads.

Replace stat() entirely with open() + handle.stat(). On Linux (Docker),
open() succeeds for directories, so handle.stat().isDirectory() detects
them without a standalone stat() call. This removes the FileCheck node
from the data-flow graph, eliminating the alert at its source.
@

* @
fix(docker): break CodeQL path alias chain between open() calls

CodeQL js/file-system-race pairs two open() calls when their path
arguments are data-flow aliased. The previous approach derived
the fallback path from the request path (resolve(initialPath,
index.html)), creating an alias chain the analyzer could trace.

Restructure so the SPA fallback uses a module-level constant
(spaFallback = resolve(root, index.html)) with zero data-flow
from the request. The two open() calls now have provably
independent path arguments, eliminating the FileCheck/FileUse pair.

Also simplifies the logic: for an SPA, all non-file requests serve
root/index.html — no directory/index.html detection needed since
the client-side router handles subroutes.
@

---------

Co-authored-by: Test <test@example.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-25 11:21:11 +01:00
henry201605
fbf7bb0b0b
Merge branch 'main' into fix/fts-non-fatal-in-analyze 2026-05-25 18:18:59 +08:00
Sparsh
73a6a5376e
fix(cpp): thread call-site types into qualified member lookup (#1632) (#1810)
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
* fix(cpp): thread call-site types into qualified member lookup (#1632)

Widen Callsite (arity optional, add argumentTypes) and add optional
callsite?: Callsite to ScopeResolver.resolveQualifiedReceiverMember.
receiver-bound-calls.ts passes the ReferenceSite through structurally;
resolveCppQualifiedNamespaceMember forwards it to narrowOverloadCandidates
along with cppConversionRank, enabling exact-type and conversion-rank
disambiguation across inline-namespace children.

Behavior change:
- outer::foo(42) where v1 declares foo(int) and v2 declares foo(double)
  now resolves to v1::foo (was: 0 edges, conservatively suppressed).
- Same-name same-normalized-signature (e.g. foo(int) vs foo(long)) still
  suppresses at 0 edges via isOverloadAmbiguousAfterNormalization.
- ADL using-import path (resolveAdlCandidates) unchanged — passes no
  callsite, narrowing degrades to existing pass-through behavior.

Closes #1632. Part of #1564.

* fix(cpp): update legacy parity expected-failure list for #1632

- Remove stale expected-failure entry for old diff-sigs test name
  (test now expects 1 edge; legacy DAG also emits 1 edge)
- Add entry for normalized-signature ambiguity (int vs long) test
- Rename describe block from 'conservative suppress' to
  'distinct signatures resolved via call-site types'

Verified both modes:
  REGISTRY_PRIMARY_CPP=1: 241/241 passed
  REGISTRY_PRIMARY_CPP=0: 194 passed, 47 skipped, 0 failed
2026-05-25 07:11:47 +01:00
Lucas van Staden
1c4993251c
fix(php): synthesize module scope for namespace-less PHP files (.phtml) (#1801)
* fix(php): phtml scope synthesis with full-file range + O(1) Step 4 lookup (#1801, #1803)

Address PR #1801 review findings and complete #1803 fix:

scope-extractor.ts:
- Synthetic Module scope uses full-file range (computed from existing
  drafts) so positionIndex containment works for top-level references
  in ERROR-root .phtml files
- Orphan scope re-parenting done on drafts in extract() by replacing
  with new drafts — no mutation of readonly fields, no PHP-specific
  logic in shared buildScopeTree
- Dead matchCount parameter removed from ensureModuleScope

namespace-siblings.ts:
- Step 4 parsedFiles.find() replaced with pre-built Map for O(1) lookup
  (was O(n²) with 16K files = ~256M comparisons)

* test(php): add pipeline benchmark for scaling regression detection

Synthetic PHP fixture generator (N files × M namespaces × K classes)
with cross-namespace imports and calls. Measures wall-clock, peak heap,
node/edge counts at 100/250/500 file scales with worker pool enabled.

Results on current branch:
- 100 files: 982ms, 65MB (9.8ms/file)
- 250 files: 1310ms, 70MB (5.2ms/file)
- 500 files: 2006ms, 92MB (4.0ms/file)
- Scaling: sublinear (0.53x-0.77x ratio)

Gated behind GITNEXUS_BENCH=1 so it does not run in normal CI.

* chore: trigger CI

* fix: prettier formatting + update scope-extractor test for synthesis behavior

* fix: extend synthetic Module range to all captures + update integration test

Address CI failure and review findings:
- ensureModuleScope now computes range from ALL captures (scope,
  declaration, reference, type-binding) not just scope drafts. This
  ensures top-level references after the last inner scope are covered.
- Update parse-worker-scope-integration test for synthesis behavior.
- Update extract() docstring to document synthesis contract.

---------

Co-authored-by: Test <test@example.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
2026-05-24 20:37:17 +01:00
Gergő Magyar
06c3fb360d
fix(group): move manifest/workspace extraction before closeLbug (#1802) (#1807) 2026-05-24 19:53:07 +01:00
Lucas van Staden
2006a3e5ac
fix(php): reduce memory during deferred-call accumulation and scope-resolution (#1800) 2026-05-24 15:53:20 +01:00
Gergő Magyar
66f9ec8eff
feat(java): add Java to MIGRATED_LANGUAGES with 100% scope-resolution parity (#1805)
* feat(java): add Java to MIGRATED_LANGUAGES with 100% scope-resolution parity

Route Java through the scope-resolution pipeline instead of the legacy
single-threaded call processor, fixing the analyze hang on large Java
codebases (issue #1741).

Changes:
- Add Java to MIGRATED_LANGUAGES (registry-primary-flag.ts)
- Add tree-sitter queries for var type inference (call-result, alias,
  field-access, enhanced-for), instanceof/switch pattern bindings,
  and method references (User::getName, this::save, User::new)
- Fix importedName to use simple class name instead of FQN so
  finalize binding materialization matches correctly
- Implement buildJavaMro with IMPLEMENTS edge transitive closure
  for interface default method resolution
- Implement populateJavaPackageSiblings for same-package implicit
  class visibility across files
- Implement cross-file return-type mirroring from imported class
  files via populateRangeBindings hook
- Add var type binding post-processing in captures.ts to resolve
  call-result and alias chains from same-file return types
- Add variable-aware argument type inference for overload resolution
- Fix pickConstructorOrClass to walk child scopes for Constructor
  defs (scope-resolution places them in Function scopes)
- Remove over-aggressive field_access suppression in shouldEmitReadMember
  so ACCESSES edges emit for field steps in method chains
- Enable collapseMemberCallsByCallerTarget for legacy parity
- Update unit tests to use Ruby as unmigrated language example

Parity: 178/178 integration tests pass in both registry-primary
and legacy modes.

* fix(java): address code review findings for scope-resolution migration

- pickConstructorOrClass: skip inner Class scopes when walking
  children for Constructor defs (prevents resolving to wrong
  constructor in nested-class scenarios)
- populateJavaCrossFileReturnTypes: filter out parameter-annotation
  bindings from class-scope mirroring to prevent foreign parameter
  types from shadowing local variables
- resolveVarTypeBindings: detect ambiguous names (overloaded methods
  with different return types, same-named variables across scopes)
  and skip resolution rather than last-write-wins
- sharedPrefixLength renamed to sharedSegmentCount: segment-based
  directory proximity for deterministic sort ordering
- Add MAX_PACKAGE_FILES cap (500) to skip O(N^2) package-siblings
  injection for pathologically large packages

* perf(java): optimize hot paths in scope-resolution migration

- Replace O(D^2) list.some() dedup with O(1) Set lookup in
  populateJavaPackageSiblings binding injection
- Replace queue.shift() O(N) with index-based O(1) iteration
  in closeInterfaces BFS traversal
- Cache sharedSegmentCount results per file in sort comparator
  to avoid redundant path splitting

* perf(ingestion): skip deferred accumulation for registry-primary languages

The legacy call/import/heritage processing path accumulates extracted
data from ALL files during the parse phase, then skips registry-primary
files one-by-one during processing.  For a 25K-file Java codebase this
wastes ~150 MB holding calls that are never consumed.

Gate the accumulation with a per-chunk file-path cache: calls, imports,
heritage, constructor bindings, and assignments for registry-primary
languages (Java, Python, TypeScript, Go, C#, C, C++, PHP, JavaScript,
Kotlin) are no longer pushed into the deferred arrays.  The scope-
resolution pipeline handles these languages independently.

Verified: 2258/2258 resolver integration tests pass across all languages.

* fix(java): address Codex adversarial review findings

- Cross-file return binding: detect ambiguous method names across
  imported classes (two classes with same-named methods but different
  return types) and delete the binding rather than first-wins
- Package-siblings: only inject top-level classes (parent is Module
  scope) to prevent nested/inner classes from leaking to package scope
- Add diagnostic log when MAX_PACKAGE_FILES cap fires so operators
  know same-package visibility was disabled for a large package

* fix(test): force REGISTRY_PRIMARY_JAVA=false in legacy call-processor unit tests

Three call-processor test suites use .java file paths to exercise
legacy DAG features (MRO fast path, interface dispatch, class lookup
fallback).  Now that Java is in MIGRATED_LANGUAGES, the call-processor
skips Java files.  Force the flag off in beforeEach/afterEach so the
legacy path runs, matching the existing Python pattern in the same file.

---------

Co-authored-by: Test <test@example.com>
2026-05-24 14:29:26 +01:00
Gergő Magyar
ac9a2ee12f
chore(ci): consolidate parity shards and narrow cross-platform matrix (#1798)
* chore(ci): reduce CI runner-minutes by consolidating parity and narrowing cross-platform

Scope-resolution parity previously spawned 9 separate GitHub Actions jobs
(one per migrated language), each doing full checkout + npm ci + build for
a single test file. Consolidate into one job running scripts/run-parity.ts
which loops through all migrated languages sequentially — same coverage,
~45 fewer runner-minutes of redundant setup per PR.

Cross-platform (Windows/macOS) previously ran the full 373-file test suite.
Narrow to 45 platform-sensitive files (native LadybugDB, process spawning,
path separators, worker threads, filesystem behavior). Full suite still runs
on Ubuntu with coverage.

Also adds 2 missing lbug integration tests (lbug-orphan-sidecar-recovery,
lbug-readonly-init) to the sequential lbug-db vitest project where they
belong, and rewrites TESTING.md to document all test lanes.

* fix: address code review findings on parity and cross-platform scripts

- Capture stderr in run-parity.ts (vitest writes diagnostics to stderr)
- Lower per-invocation timeout from 5min to 60s to stay within CI job limit
- Add --language flag validation (error on missing value)
- Add timeout diagnostic to run-cross-platform.ts catch block
- Add analyze-wal-checkpoint-failure.test.ts to lbug-db sequential project
- Expand cross-platform list: parser-loader, pipeline, pipeline-graph-golden,
  setup-skills, cli/tool-no-index-stderr (51 files, was 45)

* fix: add shell:true for Windows npx resolution and simplify fs import

execFileSync('npx', ...) fails with ENOENT on Windows because npx is
npx.cmd — shell:true resolves this. Also replaces dynamic await
import('fs') with static import, and fixes timeout detection to use
err.killed instead of err.code.

* fix(ci): raise parity per-invocation timeout to 120s and job timeout to 30min

TypeScript and C++ resolver tests take 60-90s on CI runners, exceeding
the 60s per-invocation timeout. Raise to 120s. Also bump the job-level
timeout from 25 to 30 minutes for margin (realistic total is ~11 min).

* fix(ci): raise parity per-invocation timeout to 180s for C++ resolver

C++ resolver tests take 130-150s on CI runners due to template
metaprogramming, ADL, and SFINAE fixture volume. 120s was still too
tight. Realistic total across all 9 languages is ~12 min, well under
the 30-min job timeout.

* fix(ci): use stdio inherit for parity — no per-invocation timeout

Switch from piped stdio with per-invocation timeouts to stdio: 'inherit'.
Vitest output streams to CI console in real time, making failures
immediately visible. The CI job-level timeout (30 min) is the only
guard — no more artificial per-invocation timeouts that cut off slow
resolver tests like C++ (which genuinely takes 3+ minutes).

---------

Co-authored-by: Test <test@example.com>
2026-05-24 12:10:10 +01:00
Gergő Magyar
65853e4df5
Merge branch 'main' into fix/fts-non-fatal-in-analyze 2026-05-24 09:52:29 +01:00
ManniX-ITA
39e9b40136
fix(windows): pass windowsHide:true to every child_process spawn-family call (#1794)
* fix(hooks): pass windowsHide:true to every spawnSync to suppress flashing console windows on Windows

On Windows, every PostToolUse and Stop event from Claude Code (and
the Cursor integration variant) cold-spawns ``node`` / ``npx.cmd`` /
``git`` / ``lsof`` through ``child_process.spawnSync``. Without
``windowsHide: true`` in the options, Node's child_process module
asks ``CreateProcess`` to use ``STARTF_USESHOWWINDOW`` with
``SW_SHOWDEFAULT``, and a black console window flashes onto the
user's desktop for the duration of the call. Under active
editor / agent use this means a near-continuous stream of pop-up
windows — unusable in practice (reported live on a Windows 11
workstation running the gitnexus Claude plugin against an active
project; the flashes stack on the taskbar and steal focus from the
editor).

The Node fix is one option flag per spawnSync:

    spawnSync(cmd, args, {
        encoding: 'utf-8',
        timeout,
        cwd,
        stdio: ['pipe', 'pipe', 'pipe'],
        windowsHide: true,            // <-- new
    });

``windowsHide`` is a no-op on macOS/Linux (Node docs: "Hide the
subprocess console window that would normally be created on Windows
systems"), so the patch is platform-neutral and zero-risk on the
other two majors.

This commit touches every ``spawnSync`` call in the three sources
that ship the hook layer:

* gitnexus/hooks/claude/gitnexus-hook.cjs            (4 sites)
* gitnexus/hooks/claude/hook-db-lock-probe.cjs       (3 sites)
* gitnexus-claude-plugin/hooks/gitnexus-hook.js      (6 sites)
* gitnexus-claude-plugin/hooks/hook-db-lock-probe.cjs (3 sites)
* gitnexus-cursor-integration/hooks/gitnexus-hook.cjs (3 sites)

Total: 19 spawn sites guarded. ``hook-lock.cjs`` / ``hook-lock.js``
don't spawn subprocesses; nothing else in the hooks/ dirs touches
``child_process``.

Verified on Windows 10 22H2 / Node 22.21 / gitnexus 1.6.5 by
installing the locally-built tarball and running an active Claude
Code session against a large mixed-language repo — no console
window appears for any hook fire (pre-fix: ~2-3 visible flashes per
edit). No behavioural change on Linux/macOS hosts.

* test(hooks): regression — every hook spawnSync paired with windowsHide:true

Source-level assertion that every ``spawnSync`` invocation in the
hook layer has a matching ``windowsHide: true`` in its options
object. Without the flag, Node's child_process module asks
CreateProcess to use STARTF_USESHOWWINDOW with SW_SHOWDEFAULT and
a black console window flashes onto the user's desktop for the
duration of each call — see the parent fix commit.

The check is source-level rather than behavioural because:

* the flag's effect is observable only on Windows;
* GitHub Actions runs vitest on Linux for the hook tests;
* regressing this is easy (every new spawnSync site has to remember
  to add the flag), and a runtime check on a Windows-only CI leg
  would still let a PR land on the main branch first.

Counts spawnSync occurrences and windowsHide:true occurrences per
file (in code, ignoring comments) and asserts equality. Five files
covered:

* gitnexus/hooks/claude/gitnexus-hook.cjs
* gitnexus/hooks/claude/hook-db-lock-probe.cjs
* gitnexus-claude-plugin/hooks/gitnexus-hook.js
* gitnexus-claude-plugin/hooks/hook-db-lock-probe.cjs
* gitnexus-cursor-integration/hooks/gitnexus-hook.cjs

Adding a new hook file requires updating the HOOK_FILES tuple. A
sanity assertion ``spawnCount > 0`` catches accidental deletion of
all spawn calls in a future refactor (would otherwise silently make
the count-equality assertion trivially true).

Sits next to the existing "no shell: true" and ".cmd extension"
regression tests in test/unit/hooks.test.ts — same shape, same
spirit.

* fix(src): extend windowsHide:true to every spawn-family call in cli/core/mcp/server

Companion to the hook-layer fix in this branch's first commit. The
same Windows console-window flash bug applies to every
``spawn`` / ``spawnSync`` / ``execFile`` / ``execFileSync`` /
``execFileAsync`` / ``execSync`` call in the source tree — not just
the hooks. The MCP local backend
(``src/mcp/local/local-backend.ts``) and the ``gitnexus serve`` git
helpers (``src/server/git-clone.ts``) are particularly bad because
they run from daemonized processes that have no parent console; the
spawned child auto-allocates one and it pops onto the user's
desktop. The CLI sites are less visible (the user is at a terminal
with an existing console; ``stdio: 'inherit'`` shares it) but the
flag is harmless there — windowsHide only suppresses NEW console
allocation, an inherited parent console is untouched. The visible
output of ``gitnexus analyze`` and friends is preserved verbatim.

The pre-existing fix at ``src/core/lbug/extension-loader.ts:96``
established the convention in this codebase. This commit applies it
uniformly.

Sites covered (21 new):

| File | Sites |
|---|---|
| src/cli/analyze.ts           | 1 |
| src/cli/setup.ts             | 2 |
| src/cli/wiki.ts              | 3 |
| src/core/embeddings/embedder.ts | 1 |
| src/core/git-staleness.ts    | 3 |
| src/core/run-analyze.ts      | 1 |
| src/core/wiki/cursor-client.ts | 2 |
| src/core/wiki/generator.ts   | 3 |
| src/mcp/local/local-backend.ts | 2 |
| src/server/git-clone.ts      | 2 |
| src/core/lbug/extension-loader.ts | (already had it, untouched) |

Combined with the 19 hook sites from the first commit + the 1
pre-existing extension-loader site, the codebase now has uniform
``windowsHide: true`` on every spawn-family call.

Behavioural notes:

* ``windowsHide`` is documented by Node as a no-op on POSIX —
  Linux/macOS hosts see byte-identical behaviour.
* ``stdio: 'inherit'`` callers (e.g. ``cli/wiki.ts:522`` opens the
  editor in the user's terminal) keep their interactive UX. The
  child inherits the parent's stdio handles; no new console is
  allocated; the flag has nothing to hide.
* Piped callers (``stdio: ['pipe',…]``) continue to deliver every
  byte of stdout/stderr back to the parent for the parent to log
  / process / re-print. No output is swallowed.
* ``execSync`` / ``execFileSync`` callers that previously had no
  ``stdio`` option (e.g. ``generator.ts:887`` ``execSync('git
  rev-parse HEAD', { cwd })``) keep their default pipe semantics
  (``.toString()`` still works) — windowsHide is added alongside
  the existing ``cwd`` option.

Verified on Windows 10 22H2 / Node 22.21 by installing the locally
built tarball and exercising:

* MCP detect_changes via the local backend → no flash.
* gitnexus serve → no flash on git clone/clone-pull.
* gitnexus analyze interactively → output appears in terminal as
  before, no extra window.

* test(windowsHide): extend regression to every spawn-family call in src/

Companion to the src/ patch. The hooks.test.ts regression now
covers 16 files (5 hooks + 11 source files), and asserts the
invariant for every spawn-family function — not just spawnSync.

Changes:

* Generalise countSpawnCalls() to also count spawn, execFile,
  execFileSync, execFileAsync, execSync (the entire spawn-family
  surface of child_process). Skip method calls (e.g. RegExp.exec)
  via a negative-lookbehind on ``.``.
* Add SRC_FILES table with all 11 source-tree files that import
  spawn-family functions from child_process.
* Loop over [...HOOK_FILES, ...SRC_FILES] so a regression in any
  file fails the same test name.
* Tighten the assertion to ``hideCount >= spawnCount`` rather
  than strict equality, because some sites (e.g. setup.ts:534
  using execFileAsync via shell:true on Windows) may legitimately
  add windowsHide to nested option objects in future refactors.
* Sanity gate ``spawnCount > 0`` per file catches a refactor
  that deletes all spawn calls (would otherwise make the
  assertion trivially true).

Manually exercised against the patched repo:
  16 files, 28 total spawn-family calls, 28 windowsHide:true.
  All pass.

The convention to keep this list in sync: every new file in
gitnexus/src/ that imports from 'child_process' must be added to
the SRC_FILES tuple. The cost is one line per file; the benefit
is the next contributor never has to think about windowsHide
again — the test will catch a miss before merge.

* style: prettier --write on storage/git.ts + hooks.test.ts

CI quality / format job flagged two formatting issues in the
merge-resolution commit: a long single-line options object in
storage/git.ts and similar in hooks.test.ts. prettier --write
fixes both with the project's standard wrap-and-trailing-comma
style. No semantic change.

* test(git): include windowsHide in toHaveBeenCalledWith assertion

The merge-resolution commit added windowsHide:true to the
'git rev-parse --is-inside-work-tree' execSync call in
src/storage/git.ts, but the matching strict-shape assertion in
git.test.ts:31-34 still expected the pre-patch two-key options
object {cwd, stdio}. vitest's toHaveBeenCalledWith does a deep
structural match, so the extra third key flipped the assertion
to fail.

Add windowsHide: true to the expected shape. Only this one
assertion is strict; the two siblings ('passes the correct cwd'
and the no-cwd-arg case) use expect.objectContaining and
expect.any(String) and remain green without modification.

* test(setup-codex): include windowsHide in execFile shape assertions

Same root cause as the git.test.ts fix on this branch: the windowsHide
patch added windowsHide:true to the execFile() options in
src/cli/setup.ts, but three strict-shape toHaveBeenCalledWith
assertions in setup-codex.test.ts still expected the pre-patch
{shell:true} / {shell:false} two-key options. vitest does a deep
structural match, so the extra key flipped the assertions to fail
on every CI matrix leg (ubuntu coverage + macos + windows).

Adding windowsHide:true alongside the existing 'shell' key in
all three sites.

* ci: retrigger checks

go-parity failed on a flaky onnxruntime-node postinstall network timeout
(AggregateError [ETIMEDOUT] in node ./script/install), which cascaded into
the CI Gate. No code change — empty commit to re-run the pipeline.

* fix(test): strengthen windowsHide regression assertions (PR #1794 review)

- Replace toBeGreaterThanOrEqual with exact toBe per DoD §2.7
- Remove unused `m` variable in countSpawnCalls (CodeQL finding)
- Add windowsHide: true to runGit test helper for consistency

---------

Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: ManniX-ITA <35522085+ManniX-ITA@users.noreply.github.com>
Co-authored-by: Test <test@example.com>
2026-05-24 09:51:21 +01:00
Léon Simmons
a8a8a3710d
fix(lbug): skip init lock and filesystem mutations for read-only opens (#1783) (#1784)
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
`doInitLbug` unconditionally called `acquireInitLock`, which creates
`${dbPath}.init.lock` inside the workspace. On a Docker `:ro` bind
mount this fails with EROFS.

The init lock prevents a TOCTOU race during DB creation — read-only
opens never create databases and don't need it. Split the init path:

- Read-only: skip path cleanup, init lock, orphan sidecar removal,
  and mkdir. Go straight to preflightLbugSidecars (allowQuarantine:
  false) then openLbugConnection with readOnly: true.
- Writable: unchanged behavior (lock, cleanup, open).
- Shadow-replay recovery: catch EROFS/EACCES/EPERM from the writable
  fallback in ensureReadOnlyConnectionUsable and surface an actionable
  error instead of a raw filesystem exception.

Includes integration test verifying read-only open never creates
lbug.init.lock on disk.

Fixes #1783

Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
2026-05-24 08:05:27 +01:00
Gergő Magyar
62a5774909
Merge branch 'main' into fix/fts-non-fatal-in-analyze 2026-05-24 07:36:22 +01:00
azizur100389
eb69f667ab
feat(cpp): Add structured resolver suppression outcomes (#1785) 2026-05-24 07:34:40 +01:00
Gergő Magyar
ed176527e2
Merge branch 'main' into fix/fts-non-fatal-in-analyze 2026-05-23 23:38:35 +01:00
Minidoracat
2b6e7ffbd9
fix(php): avoid Blade templates entering PHP analysis (#1790) 2026-05-23 23:37:40 +01:00
Gergő Magyar
6ccbde5aaa
Merge branch 'main' into fix/fts-non-fatal-in-analyze 2026-05-23 16:46:30 +01:00
Hugo Gu
2c066d46a1
test(cli): stabilize eval-server host checks (#1786) 2026-05-23 16:45:35 +01:00
Gergő Magyar
e80acf81c3
Merge branch 'main' into fix/fts-non-fatal-in-analyze 2026-05-23 16:42:14 +01:00
dependabot[bot]
fb94dba484
chore(deps)(deps-dev): bump tsx from 4.22.0 to 4.22.3 in /gitnexus (#1789) 2026-05-23 08:49:15 +01:00
Gergő Magyar
801ad355c4
Merge branch 'main' into fix/fts-non-fatal-in-analyze 2026-05-23 08:17:27 +01:00
Hugo Gu
7fc797e2ce
feat: Support DeepSeek V4 API (#1594)
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
2026-05-23 08:05:13 +01:00
Gergő Magyar
33d15a94ae
Merge branch 'main' into fix/fts-non-fatal-in-analyze 2026-05-23 07:28:58 +01:00
Gergő Magyar
51e667808a
feat(lang-kotlin): flip Kotlin to MIGRATED_LANGUAGES + close #1756 / #1757 (refs #1746) (#1782) 2026-05-23 07:24:35 +01:00
Gergő Magyar
5c47787888
Merge branch 'main' into fix/fts-non-fatal-in-analyze 2026-05-23 06:40:51 +01:00
dependabot[bot]
84ac88a741
chore(deps)(deps): bump qs from 6.14.2 to 6.15.2 in /gitnexus (#1791) 2026-05-23 06:39:05 +01:00
Gergő Magyar
a80dfd384d
Merge branch 'main' into fix/fts-non-fatal-in-analyze 2026-05-23 06:14:58 +01:00
ChamHerry
fc6007e70b
feat(i18n): make web and CLI language-aware (#1748) 2026-05-23 06:14:24 +01:00
github-actions[bot]
ad13b955ad chore(autofix): apply prettier + eslint fixes via /autofix command 2026-05-23 02:03:40 +00:00
henry
c434acbcdb fix: address review findings — narrow catch, truthful capabilities, progress events
Addresses all 5 blocking findings from production-readiness review:

1. meta.json capabilities: Track ftsIndexed flag and write
   status='degraded' (not 'available') when FTS indexes are missing.
   Prevents false-positive capability reporting (#1715 regression).

2. Progress event gap: Emit progress('fts', 90, 'Search indexes
   degraded (BM25 unavailable)') in the verification-failure branch
   so progress never stalls at 85%.

3. Narrow catch scope: Only suppress errors containing
   'FTS extension unavailable'. DB connection failures, schema errors,
   and programming errors (e.g. safeIdentifier) now propagate as before.

4. (Finding 4 resolved by Finding 3): Non-FTS errors still throw in
   all analyze modes, so only the intended FTS-unavailable case degrades.

5. Stronger test assertions: Verify warning log content, verify
   progress does not report 'Search indexes ready', verify degraded
   progress message is emitted.
2026-05-23 09:58:39 +08:00
Copilot
87b91c821e
fix(lbug): add WAL checkpoint-threshold control (#1772)
* Initial plan

* fix(analyze): add WAL auto-checkpoint CLI control and default-off behavior

* test(analyze): share lbug auto-checkpoint parsing and align validation

* fix(analyze): always enable lbug auto-checkpoint and expose threshold control

* refactor(lbug): inline always-on auto-checkpoint constructor arg

* fix(analyze): guide checkpoint-threshold on Ladybug WAL checkpoint IO failures

* test(analyze): cover checkpoint IO guidance and add integration guard

* fix(analyze): tighten checkpoint IO detection and remove test hook

* fix(analyze): remove checkpoint test hook and tighten error matching

* fix(analyze): rename to wal-checkpoint-threshold, raise default, add manual checkpoint driver with retry

Address review feedback on PR #1772:

- Rename CLI flag, env var, AnalyzeOptions field, recovery-hint tag, and
  parser/constants from lbug-* to engine-neutral wal-* (matches the existing
  WAL_RECOVERY_SUGGESTION / isWalCorruptionError convention).
- Raise default threshold from -1 (Ladybug stock ~16 MiB) to 64 MiB so users
  on the default config no longer hit the original rename/remove race.
- Align both READMEs to publish 67108864 (64 MiB) instead of 65536 (which
  would have made the crash more frequent).
- Add wal-checkpoint-driver.ts: a periodic manual CHECKPOINT driver wrapped
  in a 3-attempt jittered retry (50/200/500 ms), driven from runFullAnalysis.
  Opt-out via GITNEXUS_WAL_MANUAL_CHECKPOINT=0. Moves the race window into a
  JS-controllable retry surface while keeping native auto-checkpoint on.
- Move LBUG_CHECKPOINT_RENAME_RE / REMOVE_RE plus the predicate (renamed to
  isLbugCheckpointIoError) into lbug-config.ts alongside isWalCorruptionError.
  Predicate is now exported. Add a permissive fallback matcher and pin the
  matched Ladybug version in comments.
- Warn instead of silently defaulting when GITNEXUS_WAL_CHECKPOINT_THRESHOLD
  is set to a non-empty unparseable value (closes the CLI-vs-env asymmetry).
- Add a typed RecoveryHint string-literal union in cli-message.ts so future
  hint tags can't drift.
- Add a real integration test under test/integration/ that triggers a
  Ladybug checkpoint IO failure via a pre-existing directory at the rename
  target (portable across platforms; no test-only injection hook).
- Add small-disk / CI caveat (32 MiB secondary suggestion) to the recovery
  hint and README env-var rows.
- Document CLI/env precedence in the analyze --help block.
- Help placeholder: <value> -> <bytes>.
- Rename analyze-lbug-auto-checkpoint.test.ts to use the new wal-* token.

* chore(lbug): remove dead jitteredDelay helper and apply prettier

- Drop unused `jitteredDelay` function flagged by CodeQL in PR #1772; the
  retry loop already inlines the same calculation with the injectable
  `randomImpl` so the helper was dead. Move the non-cryptographic-by-design
  comment next to the actual jitter site.
- Apply `prettier --write` to wal-checkpoint-driver.ts and the new
  integration test to absorb the PR autofix bot's formatting findings.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: Test <test@example.com>
2026-05-22 14:46:49 +01:00
Gergő Magyar
c7ce4d5c1f
Merge branch 'main' into fix/fts-non-fatal-in-analyze 2026-05-22 13:46:42 +01:00
azizur100389
952ada70c5
feat(cpp): Resolve overloaded operator calls (#1754)
* feat(cpp): resolve overloaded operator calls

* fix(cpp): tighten overloaded operator resolution

---------

Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
2026-05-22 13:31:06 +01:00
Gergő Magyar
060fe75715
docs(lang-kotlin): refresh scope-resolver JSDoc after #1758-#1763 landed (#1781)
The scope-resolver header comment claimed forced-mode passed 154/175
(88%) and listed smart casts, cross-file iterables, method chains,
overload selection, virtual dispatch, and interface defaults as
"remaining gaps". All six landed in PRs #1774-#1779. Forced mode now
passes 175/175 (verified post-merge against `main`).

Update the header to:
- state the current forced-mode result accurately,
- enumerate the closed sub-issues so future readers can trace each
  capability back to its PR,
- and explicitly name the remaining flip blockers (#1755, #1756,
  #1757) so the next maintainer to look at this file knows exactly
  what's required before adding `Kotlin` to `MIGRATED_LANGUAGES`.

Docs-only — no behavioral changes.

Refs #1746.

Co-authored-by: Test <test@example.com>
2026-05-22 12:44:44 +01:00
Gergő Magyar
d15f8bef54
feat(ingestion): log deferred resolution progress when verbose (#1741) (#1773)
* feat(ingestion): log deferred resolution progress when verbose

Add [deferred-profile] timing logs for post-chunk import, heritage, heritage-map, and legacy call resolution. Enabled on GITNEXUS_VERBOSE / analyze -v (and optionally GITNEXUS_PROFILE_DEFERRED) to diagnose analyze stalls on large repos (issue #1741).

Co-authored-by: Cursor <cursoragent@cursor.com>

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

* fix(ingestion): address PR #1773 production-readiness review

Move deferred call progress logs after the registry-primary skip so sites= counts match files actually resolved. Only time buildHeritageMap when heritage records exist; otherwise log an explicit skip. Add wiring tests that assert [deferred-profile] emission from buildHeritageMap and processCallsFromExtracted. Snapshot GITNEXUS_PROFILE_DEFERRED env vars in analyze CLI isolation.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ingestion): address PR #1773 code-review findings

P0
- Replace forbidden toBeGreaterThanOrEqual/toBeLessThan in
  profileElapsedMs test with exact-arithmetic vi.spyOn(hrtime.bigint)
  asserting .toBe(2.5) and .toBe(0). DoD §2.7 compliance.

P2
- Use Number() (not parseInt) when parsing
  GITNEXUS_PROFILE_DEFERRED_SLOW_MS so scientific notation like '1e9'
  doesn't silently parse to 1 and turn the slow-file log into a per-file
  log storm.
- Introduce startTimer(enabled): bigint | null and endTimer(start,
  format) helpers in deferred-resolution-profile.ts; refactor 6+
  timing blocks in parse-impl.ts and call-processor.ts to use them.
  Removes the 0n sentinel that conflated 'disabled' with 'zero
  elapsed time' and let TS narrow correctly.
- Split the call-processor file counter: filesProcessed (all iterated)
  vs resolvedFiles (post registry-primary skip). Key the every-N
  progress log and the start-of-phase log on resolvedFiles so mixed
  Python+JVM repos where the skipped language sorts first still emit
  'calls 1/1 file=...' on the first non-skipped file. Adds a wiring
  test for the mixed-language ordering case.

P3
- Restore the original isDev '🔗 E1: Seeded ...' logger.info line so
  log scrapers keyed on the emoji marker still match; emit the
  [deferred-profile] variant only when deferredProfile && !isDev.
- Move tFile = startTimer(profileCalls) below the registry-primary
  skip so skipped files don't trigger an hrtime.bigint() call.
- Document GITNEXUS_PROFILE_DEFERRED and
  GITNEXUS_PROFILE_DEFERRED_SLOW_MS in the README env-var table.

* refactor(ingestion): extract parseTruthyEnv to shared utils (U5)

Three narrow-form env-var truthy checkers (verbose.ts, registry-primary-flag.ts,
deferred-resolution-profile.ts) each had their own `'1' | 'true' | 'yes'` parser
with subtle divergences (trim or no trim, set vs disjunction). Consolidate on a
single `parseTruthyEnv(raw)` helper in utils/env.ts — the module already serves
as the centralization point for shared ingestion env constants.

logger.ts's broader `isTruthyEnv` (negative-list, pino-debug convention) stays
untouched — different intent, different semantics.

New table-driven test at test/unit/env.test.ts covers case variants,
whitespace, and rejection of falsy / unknown tokens.

* refactor(ingestion): named constants for deferred-profile log gates (U6)

Replace magic literals 10 / 100 / 3_000 / 5_000 in
deferred-resolution-profile.ts with module-private named constants
LOG_EVERY_N_VERBOSE, LOG_EVERY_N_PROFILE, DEFAULT_SLOW_MS_VERBOSE,
DEFAULT_SLOW_MS. Not exported — internal tuning knobs. Pure refactor;
existing tests assert the exact values and still pass unchanged.

* fix(ingestion): pre-pass denominator for deferred call progress (U1, A1)

The live per-file denominator in processCallsFromExtracted previously
read `totalFiles - skippedRegistryPrimaryFiles` at log time. On mixed
Python+JVM repos where the skipped language interleaves with the
resolved one, the denominator drifts upward as the loop iterates —
files iterated before later skips have been seen carry an inflated
denominator. The live ratio only self-corrects after the final file
has been classified.

Fix: one-pass pre-count over byFile.keys() before the work loop
computes resolvedTotal once. The denominator is then stable from the
first emission onward. The pre-pass runs only on the enabled path
(profileCalls=true) so the disabled path keeps zero extra work.

Adds a wiring test exercising the alternating [ts, py, ts, py, ...]
order that triggered the drift, asserting every emitted line uses
`/4` and no other denominator slips through.

* fix(ingestion): E1 enrichment log emits on both dev and profile flags (U2, A2)

The post-chunk E1 enrichment log used `if (isDev) {...} else if
(deferredProfile) {...}` which is mutually exclusive. On combined runs
(NODE_ENV=development + GITNEXUS_PROFILE_DEFERRED=1) the [deferred-
profile] line was silently swallowed — operators grepping that prefix
saw a gap between wildcard-synth and heritage timings, while the
inline comment promised dual emission.

Fix: two independent `if` statements so both branches fire when both
flags are set. The original emoji-prefixed `🔗 E1: Seeded` line keeps
its phrasing for any dev-mode log scrapers that depend on the marker.

Pinning test (parse-impl-e1-emission-shape.test.ts) reads the source
and asserts (a) both branches exist as standalone `if` statements and
(b) the closing `}` of the isDev branch is followed by `if`, not
`else if`. Source-shape pins are the right test scope for a purely
structural change — the regression we are guarding against is exactly
how a future reader greps for it.

* feat(ingestion): unresolved-side counters in heritage-map profile (U7)

The existing maxNameCartesian / ambiguousHeritageRecords counters in
buildHeritageMap only observed records where BOTH the child and parent
name lookups resolved. On JVM monorepos the actual pathological case is
one side empty (typically an unresolved external supertype with many
same-named children, or vice versa) — those records were silently
dropped from the metric.

Add `unresolvedChildLookups` and `unresolvedParentLookups` in a
separate `if (profileHeritage)` block placed immediately after the two
`lookupClassByName` calls (so it observes the unresolved cases the
length-guarded ambiguity block below cannot see). Both counters reuse
the existing childDefs / parentDefs values — no additional lookups.

Done-summary log extended to include the two new counters. Wiring test
covers both directions (unresolved parent, unresolved child) plus the
existing "both resolved" baseline now asserts the new counters report
zero for that case.

* fix(ingestion): endTimer formatter exception safety (U3)

Wrap the format callback in endTimer in a try/catch so a throwing
formatter (custom toString, JSON.stringify on a circular object,
future heavier serializers) cannot abort the deferred resolution
band. Observability code must never escalate to a load-bearing
failure mode.

On catch we emit a single `[deferred-profile] formatter error: …`
line via logDeferredProfile and return; the caller's stage continues
as if profiling had no-op'd for this timer. DoD §2.8 is satisfied —
the failure is surfaced, not silently swallowed.

Tests cover the four cases: happy path emits the formatted line, null
start no-ops without invoking the formatter, throwing formatter is
caught and surfaces one error line, non-Error throws are coerced via
String() in the message.

* fix(ingestion): defensive wrap + dropped-line counter for logDeferredProfile (U4)

Wrap logger.info inside logDeferredProfile in a try/catch so a throwing
underlying logger cannot abort the deferred resolution band. Pino with
sync:false (the current SonicBoom destination) does not throw
synchronously for `info(string)` calls, but first-use construction
paths (pino-pretty resolve, level validation) and any future transport
reconfiguration could. The wrap is belt-and-suspenders coverage; the
counter makes silent failures visible.

A module-private droppedLogLines counter accumulates dropped lines.
Two helpers — getDeferredProfileDroppedCount() and
resetDeferredProfileDroppedCount() — expose the counter. The handler
deliberately does NOT call the failing logger; that would risk an
infinite loop if the failure is steady-state.

processCallsFromExtracted resets the counter at entry (so each analyze
run gets a fresh count rather than accumulating across the process
lifetime — relevant for the MCP server, eval harness, integration
tests), and surfaces the count in the done-summary as `note: N profile
log lines dropped (logger errors)` when greater than zero. DoD §2.8
(no silent diagnostic catches) is satisfied.

Tests cover the helper API (zero at entry, idempotent reset) and the
happy path; the catch arm is pinned via source-shape assertion since
the logger Proxy can't be vi.spyOn'd directly (lazy `get` trap, no
own-property to wrap).

* docs(readme): clarify GITNEXUS_PROFILE_DEFERRED_SLOW_MS coercion (U8)

The env-var row mentioned integer / scientific notation only, but the
underlying parser (`Number(raw)` since the U2 fix in PR #1773) also
accepts decimals like `.5` and hex like `0x10`. Document the actual
acceptance set plus the non-finite / non-positive fallback so operators
setting unusual values know what to expect.

---------

Co-authored-by: Test <test@example.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-22 12:37:30 +01:00
henry201605
cfc2b7d463
Merge branch 'main' into fix/fts-non-fatal-in-analyze 2026-05-22 17:57:42 +08:00
henry
fc49ccc84c test: update FTS verify test to match non-fatal behavior
The test previously asserted that FTS verification failure throws during
full analyze. Since FTS failure is now non-fatal (graceful degradation),
update the test to verify analyze completes successfully with a warning
instead of throwing.

The repair-mode test (--repair-fts) still asserts a throw, which is
correct: if the user explicitly requests FTS repair, failure should be
reported.
2026-05-22 17:48:54 +08:00
Gergő Magyar
f72d9a99c6
fix(lang-kotlin): virtual dispatch via constructor type override (#1762) (#1778)
Closes #1762. `val animal: Animal = Dog(); animal.speak()` resolved
to `Animal.speak` (or no edge to Dog at all) under
`REGISTRY_PRIMARY_KOTLIN=1` because the Kotlin scope query emits BOTH
an annotation type-binding (`animal -> Animal`) and a constructor-
inferred type-binding (`animal -> Dog`). The generic scope-extractor
ranks annotation sources higher than constructor-inferred sources (see
`typeBindingStrength` in scope-extractor.ts), so the annotation
always won and `animal.speak()` dispatched against the static type.

Kotlin's virtual dispatch semantics expect the dynamic type — the
overriding `Dog.speak` should win when the RHS is a constructor call,
because that's what runs at runtime.

Fix: in `emitKotlinScopeCaptures`, suppress the `@type-binding.
annotation` capture when the underlying `property_declaration` has a
`call_expression` value sibling. The constructor-inferred capture
remains, becomes the sole binding for the variable, and receiver-bound
resolution dispatches against the constructed class (and walks its
MRO).

This is intentionally Kotlin-specific — flipping precedence globally
would change behavior for other languages whose static-type
annotations are still the right binding when present. Kotlin is the
language where the constructor RHS is the dispatch target by design.

Verification (REGISTRY_PRIMARY_KOTLIN=1):
- Forced-mode: 21 -> 20 failing of 175 (1 fewer; test 1715 in
  `test/integration/resolvers/kotlin.test.ts` now green).
- Default-mode Kotlin: 175/175 unchanged.
- Full resolver suite: 2216/2216 unchanged.
- Remaining 20 failures are tracked by sibling sub-issues
  (#1758, #1759, #1760, #1761, #1763).

Does NOT add Kotlin to MIGRATED_LANGUAGES per parent #1746 flip criteria.

Closes #1762. Refs #1746.

Co-authored-by: Test <test@example.com>
2026-05-22 10:40:59 +01:00
Gergő Magyar
64efc202f6
fix(lang-kotlin): method-chain fixpoint receiver types (#1760) (#1776)
Closes #1760. Multi-step intra-file chains like

    val user = getUser()
    val addr = user.address
    val city = addr.getCity()
    city.save()

produced no `CALLS` edge for `city.save()` because the Kotlin extractor
only inferred property types for `simple_identifier` values (`val x = y`)
and call expressions with simple-identifier callees (`val x = fn()`).
Navigation expressions (`val addr = user.address`) and call expressions
with navigation-expression callees (`val city = addr.getCity()`)
returned null, leaving `addr` and `city` unbound — the chain broke
two hops before `city.save()`.

Implementation:
- `collectKotlinClassMembers(rootNode)` indexes per-file class fields
  (primary-constructor `val`/`var` params + body property declarations)
  and method return types. Per-file scope matches the existing
  extractor design.
- `inferKotlinPropertyType` gains two new cases:
  1. `navigation_expression` value — receiver type via `localTypes`,
     field type via `classMembers.fields`.
  2. `call_expression` with `navigation_expression` callee — receiver
     type via `localTypes`, method return type via `classMembers.methods`.
  Both return null when any link is unknown (safe / over-conservative).

Verification (REGISTRY_PRIMARY_KOTLIN=1):
- Forced-mode: 21 -> 20 failing of 175 (1 fewer; test 1491 in
  `test/integration/resolvers/kotlin.test.ts` now green).
- Default-mode Kotlin: 175/175 unchanged.
- Full resolver suite: 2216/2216 unchanged.
- Remaining 20 failures are tracked by sibling sub-issues
  (#1758, #1759, #1761, #1762, #1763).

Does NOT add Kotlin to MIGRATED_LANGUAGES per parent #1746 flip criteria.

Closes #1760. Refs #1746.

Co-authored-by: Test <test@example.com>
2026-05-22 10:40:46 +01:00
Gergő Magyar
67cc4c6d94
fix(lang-kotlin): cross-file iterable return propagation (#1759) (#1775)
Two related bugs surfaced in REGISTRY_PRIMARY_KOTLIN=1 forced mode:

1. `import models.getRepo` silently resolved to `models/User.kt` (the
   first `.kt` file inside `models/` by iteration order) when no file
   was named after the symbol. `findKotlinFile` returned a single
   directory child as a fallback, so the importer's module-scope mirror
   only ever picked up the first arbitrary candidate — `getUser → User`
   landed but `getRepo → Repo` never did, and downstream `repo.save()`
   resolution fell through to no edge.

2. `for (x in importedCallable())` produced no for-loop type binding
   when the callee's return type lived in another file, because
   `inferKotlinIterableElementType`'s call-expression arm consulted
   only the local file's `returnTypes` map.

Fix:
- Split `findKotlinFile` into `findKotlinExactOrSuffix` (exact / suffix
  match only) and `findKotlinDirectoryChild` (legacy single-child
  fallback). Add `findKotlinPackageFiles` returning every `.kt`/`.kts`
  file inside a package directory. The resolver now fans out the
  stripped path through `findKotlinExactOrSuffix → findKotlinPackageFiles`,
  returning a `readonly string[]` candidate set. The finalize pass
  walks each candidate and picks the one whose `localDefs` actually
  export the imported name — exactly the multi-target contract
  `FinalizeHooks.resolveImportTarget` already supports.
- `inferKotlinIterableElementType` for `call_expression` now falls
  back to the callee's identifier text when the local return-type map
  has no entry. `propagateImportedReturnTypes` chain-follows
  `loopvar → callee → ElementType` once the imported `callee → Element`
  mirror lands at module scope (which now works thanks to fix #1).

Verification (REGISTRY_PRIMARY_KOTLIN=1):
- Forced-mode: 21 -> 18 failing of 175 (3 fewer; tests 487, 1242, 1251
  in test/integration/resolvers/kotlin.test.ts now green).
- Default-mode Kotlin: 175/175 unchanged.
- Full resolver suite: 2216/2216 unchanged (incl. `kotlin-calls`
  `util.OneArg.writeAudit` regression check at line 176).
- Remaining 18 failures are tracked by sibling sub-issues
  (#1758, #1760, #1761, #1762, #1763).

Does NOT add Kotlin to MIGRATED_LANGUAGES per parent #1746 flip criteria.

Closes #1759. Refs #1746.

Co-authored-by: Test <test@example.com>
2026-05-22 10:40:32 +01:00
henry201605
7b75cfb585
Merge branch 'main' into fix/fts-non-fatal-in-analyze 2026-05-22 17:24:37 +08:00
Gergő Magyar
a3e7dfa8a6
fix(lang-kotlin): interface default method dispatch via implements-split MRO (#1763) (#1779)
Closes #1763. `user.validate()` on `class User : Validator` resolved
to no edge under REGISTRY_PRIMARY_KOTLIN=1 when validate() was a
default method declared on the Validator interface:

    class User(val name: String) : Validator
    interface Validator { fun validate(): Boolean = true }
    fun run() { val user = User("alice"); user.validate() }

The generic `buildMro` walks EXTENDS edges only. Kotlin classes
implement interfaces via IMPLEMENTS edges (per the parsing-processor),
so the implementor's MRO never picked up the interface's default
methods — `findOwnedMember(User, validate)` returned undefined and
no fallback walked to Validator.

Fix: replace `defaultLinearize` with a Kotlin-specific MRO builder
modeled after PHP's `buildPhpMro` (trait composition):
1. Run the generic `buildMro` (EXTENDS-only).
2. Collect direct IMPLEMENTS edges as class -> interface[] map.
3. For each class, walk its EXTENDS-MRO ancestors AND its own
   IMPLEMENTS edges to seed interface candidates, then BFS-close to
   pick up transitive interface inheritance (interface A : B).
4. Append the interface closure to the class's MRO (after the EXTENDS
   chain — Kotlin requires explicit override on conflict, so this
   ordering is a safe approximation for method lookup).
5. Classes with no EXTENDS but with IMPLEMENTS edges (the #1763
   fixture shape) get their MRO seeded directly from their interfaces.

Verification (REGISTRY_PRIMARY_KOTLIN=1):
- Forced-mode: 21 -> 20 failing of 175 (1 fewer; test 2062 in
  `test/integration/resolvers/kotlin.test.ts` now green).
- Default-mode Kotlin: 175/175 unchanged.
- Full resolver suite: 2216/2216 unchanged.
- Remaining 20 failures are tracked by sibling sub-issues
  (#1758, #1759, #1760, #1761, #1762).

Does NOT add Kotlin to MIGRATED_LANGUAGES per parent #1746 flip criteria.

Closes #1763. Refs #1746.

Co-authored-by: Test <test@example.com>
2026-05-22 10:24:02 +01:00
henry
689f79acc1 fix(analyze): make FTS index creation non-fatal during analyze
When the LadybugDB FTS extension cannot be loaded (e.g. macOS 12 where
libc++ lacks std::to_chars(double), or container environments without
the native extension), `gitnexus analyze --embeddings` fails at 85%
and aborts before embedding generation can run.

This is inconsistent with the MCP server's behavior: pool-adapter.ts
already treats FTS load failure as a graceful degradation (ftsLoaded =
false, BM25 search degrades, other features continue).

Change: wrap createSearchFTSIndexes() in try/catch so that:
- FTS creation failure logs a warning instead of throwing
- Embedding generation (Phase 4) proceeds normally
- `--repair-fts` remains available for explicit FTS retry
- BM25 keyword search degrades gracefully (same as MCP read path)

Affected users: macOS 12 (Monterey), any platform where the FTS
extension binary references symbols missing from the system libc++.
2026-05-22 16:57:49 +08:00
Gergő Magyar
ccf0b8b73c
fix(lang-kotlin): smart-cast type refinement for when/is and if/is (#1758) (#1774)
Adds tree-sitter @scope.block captures for Kotlin when-arm bodies and
if-then bodies, plus a synthesizer that emits narrowed type-bindings
anchored on those bodies. The receiver-bound calls pass then resolves
`obj.member()` inside `is T` arms against `T` without leaking the
narrowing to sibling arms, `else` branches, or the enclosing function.

Implementation:
- query.ts: @scope.block on `(when_entry (when_condition (type_test))
  (control_structure_body))` and `(if_expression (check_expression)
  (control_structure_body))`.
- captures.ts: synthesizeKotlinSmartCastBindings walks `when_expression`
  and `if_expression` nodes; emits `@type-binding.annotation` with a
  `@type-binding.narrowed` marker so kotlinBindingScopeFor in
  simple-hooks.ts overrides the scope-extractor's auto-hoist (which
  would otherwise promote unbraced-arm bindings to the function scope
  because the body anchor coincides with the Block scope's range).
- simple-hooks.ts: kotlinBindingScopeFor checks the marker and pins the
  binding to the innermost (Block) scope.

Verification (REGISTRY_PRIMARY_KOTLIN=1):
- Forced-mode: 21 -> 9 failing of 175 (12 fewer; all 12 when/is tests
  now green: lines 957, 966, 975, 1096, 1107, 1118, 1131, 1142, 1153,
  1164, 1182, 1195 in test/integration/resolvers/kotlin.test.ts).
- Default-mode: 175/175 unchanged.
- Full resolver suite: 2216/2216 unchanged.
- Remaining 9 failures are tracked by sibling sub-issues (#1759-#1763).

Does NOT add Kotlin to MIGRATED_LANGUAGES per parent #1746 flip criteria.

Closes #1758. Refs #1746.

Co-authored-by: Test <test@example.com>
2026-05-22 09:44:19 +01:00
Gergő Magyar
9ad48c173e
fix(lang-kotlin): overload target-id selection by parameter types (#1761) (#1777)
Same-arity Kotlin class-method overloads collapsed onto whichever node
was registered first. `lookup("alice")` resolved to `lookup(Int)` —
not because the picker chose wrong, but because `resolveDefGraphId`
fell through to the simple-name fallback after its parameter-typed key
lookup missed.

Root cause: `populateKotlinOwners` (which calls
`populateClassOwnedMembers`) assigned `ownerId` and qualified names to
class-owned function defs but left `def.type === 'Function'`. The
graph parsing-processor, in contrast, emits a `Method` node label for
class members. `resolveDefGraphId`'s parameter-typed key lookup is
gated on `def.type === 'Method'` (graph-bridge/ids.ts:108-116), so it
was skipped for every Kotlin class method. With the type-keyed lookup
skipped, the resolver fell through to `simpleKey`, which is
first-wins by registration order — and the Int overload always
registered first in these fixtures.

Fix: `populateKotlinOwners` now upgrades `def.type` from `Function`
to `Method` after `populateClassOwnedMembers` assigns `ownerId`. This
aligns the scope-resolution model with the graph's node labels so
parameter-typed key registration and lookup operate in the same
keyspace.

Picker logic in `pickImplicitThisOverload` / `narrowOverloadCandidates`
was already correct — verified by trace: it narrowed `lookup("alice")`
to the `[String]` def. Only the graph-id lookup was broken.

Verification (REGISTRY_PRIMARY_KOTLIN=1):
- Forced-mode: 21 -> 18 failing of 175 (3 fewer; tests 1620, 1659,
  1692 in `test/integration/resolvers/kotlin.test.ts` now green).
- Default-mode Kotlin: 175/175 unchanged.
- Full resolver suite: 2216/2216 unchanged.
- Remaining 18 failures are tracked by sibling sub-issues
  (#1758, #1759, #1760, #1762, #1763).

Does NOT add Kotlin to MIGRATED_LANGUAGES per parent #1746 flip criteria.

Closes #1761. Refs #1746.

Co-authored-by: Test <test@example.com>
2026-05-22 09:22:53 +01:00
Emmanuel
954b184248
fix(cli): apply --no-stats to keep-marker stats line (#1706) (#1765)
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
* fix(cli): apply --no-stats to keep-marker stats line (#1706)

The keep-marker branch of upsertGitNexusSection rebuilt the index-summary
line on every analyze and always re-injected the volatile counts,
ignoring --no-stats. For teams that commit a trimmed AGENTS.md/CLAUDE.md
with a gitnexus:keep marker, that produced recurring no-value merge
conflicts — exactly what --no-stats exists to prevent.

Thread noStats into upsertGitNexusSection. Under --no-stats the keep-path
stats line becomes "Indexed as **<name>**" with no (N symbols, ...)
parenthetical; the project name still refreshes so renames propagate.
The statsPattern parenthetical is now optional so a count-free line left
by a prior --no-stats run still matches.

* test(cli): cover count-return and AGENTS.md parity for --no-stats keep path

Addresses review findings F1 and F2 on PR #1765:

- F1: add a test that counts RETURN when --no-stats is dropped after a
  prior count-free run — guards against the flag becoming sticky.
- F2: extend the noStats+keep "drops the volatile counts" test to assert
  AGENTS.md alongside CLAUDE.md, so a future asymmetry between the two
  upsertGitNexusSection call sites is caught.

---------

Co-authored-by: Emmanuel Alawode <platforms@chowbea.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
2026-05-22 07:03:40 +01:00
Copilot
be3833d9c9
chore(security): upgrade @vercel/node in gitnexus-web and remediate transitive advisories (#1705) 2026-05-22 06:26:09 +01:00
dependabot[bot]
dc96bb048a
chore(deps)(deps-dev): bump tsx from 4.21.1 to 4.22.0 in /gitnexus (#1768) 2026-05-22 05:27:51 +01:00
Abhigyan Patwari
5a0f5e81db
ci(web): use npm ci for deterministic Vercel installs (#1764) 2026-05-22 05:08:42 +01:00