mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-17 23:52:36 +00:00
2064 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
41fa74cd84
|
chore(deps)(deps): bump zod from 4.4.3 to 4.5.4 in /gitnexus-web (#3282)
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
Bumps [zod](https://github.com/colinhacks/zod) from 4.4.3 to 4.5.4. - [Release notes](https://github.com/colinhacks/zod/releases) - [Commits](https://github.com/colinhacks/zod/compare/v4.4.3...v4.5.4) --- updated-dependencies: - dependency-name: zod dependency-version: 4.5.4 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
e9469f2149
|
chore(deps)(deps-dev): bump @babel/types in /gitnexus (#3285)
Bumps [@babel/types](https://github.com/babel/babel/tree/HEAD/packages/babel-types) from 8.0.4 to 8.0.5. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v8.0.5/packages/babel-types) --- updated-dependencies: - dependency-name: "@babel/types" dependency-version: 8.0.5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
29f4ce0592
|
chore(deps)(deps-dev): bump @babel/traverse in /gitnexus (#3284)
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 8.0.4 to 8.0.5. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v8.0.5/packages/babel-traverse) --- updated-dependencies: - dependency-name: "@babel/traverse" dependency-version: 8.0.5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
dcf6bbc11d
|
chore(deps)(deps-dev): bump @babel/parser in /gitnexus (#3283)
Bumps [@babel/parser](https://github.com/babel/babel/tree/HEAD/packages/babel-parser) from 8.0.4 to 8.0.5. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v8.0.5/packages/babel-parser) --- updated-dependencies: - dependency-name: "@babel/parser" dependency-version: 8.0.5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
8a10383563
|
chore(deps)(deps-dev): bump @babel/generator in /gitnexus (#3281)
Bumps [@babel/generator](https://github.com/babel/babel/tree/HEAD/packages/babel-generator) from 8.0.0 to 8.0.5. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v8.0.5/packages/babel-generator) --- updated-dependencies: - dependency-name: "@babel/generator" dependency-version: 8.0.5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
9ac303ad14
|
chore(deps)(deps): bump dompurify from 3.4.13 to 3.4.15 in /gitnexus-web (#3280)
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.4.13 to 3.4.15. - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](https://github.com/cure53/DOMPurify/compare/3.4.13...3.4.15) --- updated-dependencies: - dependency-name: dompurify dependency-version: 3.4.15 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
57e36560af
|
chore(deps)(deps): bump react-zoom-pan-pinch in /gitnexus-web (#3279)
Bumps [react-zoom-pan-pinch](https://github.com/BetterTyped/react-zoom-pan-pinch) from 4.0.3 to 4.2.0. - [Release notes](https://github.com/BetterTyped/react-zoom-pan-pinch/releases) - [Changelog](https://github.com/BetterTyped/react-zoom-pan-pinch/blob/master/CHANGELOG.md) - [Commits](https://github.com/BetterTyped/react-zoom-pan-pinch/compare/v4.0.3...v4.2.0) --- updated-dependencies: - dependency-name: react-zoom-pan-pinch dependency-version: 4.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
fd270fdbf5
|
chore(deps)(deps-dev): bump jsdom from 29.1.1 to 30.0.1 in /gitnexus-web (#3277)
Bumps [jsdom](https://github.com/jsdom/jsdom) from 29.1.1 to 30.0.1. - [Release notes](https://github.com/jsdom/jsdom/releases) - [Commits](https://github.com/jsdom/jsdom/compare/v29.1.1...v30.0.1) --- updated-dependencies: - dependency-name: jsdom dependency-version: 30.0.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
ed2dd6b0ad
|
chore(deps)(deps): bump langchain from 1.5.4 to 1.5.11 in /gitnexus-web (#3276)
Bumps [langchain](https://github.com/langchain-ai/langchainjs) from 1.5.4 to 1.5.11. - [Release notes](https://github.com/langchain-ai/langchainjs/releases) - [Commits](https://github.com/langchain-ai/langchainjs/compare/langchain@1.5.4...langchain@1.5.11) --- updated-dependencies: - dependency-name: langchain dependency-version: 1.5.11 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
21a52af1d4
|
fix(lbug): ship FTS per-platform and recover in-place native aborts (#3274)
* fix(lbug): pin Ladybug core so Dependabot cannot ship a skewed FTS artifact The extension version is a separate upstream constant. Ignore daily core bumps and fail the pairing gate when the committed manifest does not name the installed core. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): make doctor and CI FTS gates resolve the packaged artifact Doctor and the REQUIRE_FTS file gates still treated an empty ~/.lbdb as unavailable, which would turn three CI jobs red once analyze stops installing into that tree. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): name native-abort and tuple-missing so analyze cannot mis-advise The CLI summary's trailing else treated every unknown skip reason as a missing extension. New crash and platform causes must get their own remedies, not a network-install hint. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): delete the dead read-path FTS index create ensureFTSIndex had no production callers and swallowed read-only CREATE_FTS_INDEX failures, which hid the only signal that a reader tried to write. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): vendor per-platform FTS artifacts so analyze needs no host install Keyword search depended on a CDN fetch into ~/.lbdb. Shipping the five published tuples inside the package makes air-gapped and ignore-scripts installs load the same artifact the publish gate checksums. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): load the packaged FTS artifact before any network install Analyze still required a CDN fetch into ~/.lbdb even when the package already shipped the file. FTS now path-loads the vendored tuple first and records source labels so a later truncated home copy cannot steal the diagnosis. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): diagnose a core/extension version skew instead of a missing runtime A structurally valid FTS artifact whose path version disagrees with the packaged pin must name both versions, not prescribe VC++ or OpenSSL. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): stamp an FTS phase so repair stays usable after an in-place abort A native CREATE_FTS_INDEX abort leaves no skip reason; the next run infers it from the dirty flag, and --repair-fts must not treat that phase as a half-written graph. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): park an in-place FTS crash WAL without wiping the graph An FTS abort after a successful checkpoint must reopen the live index on macOS, Windows, and Linux. Staging never parks the live WAL; readers keep today's large-WAL refusal. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): refuse read-only opens of an FTS-poisoned WAL MCP and serve cannot repair a leftover in-place abort. Fail before the native open and name --repair-fts, on macOS, Windows, and Linux. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): name a vendor-neutral Windows OpenSSL prerequisite OQ1 is unanswered here so GitNexus does not ship OpenSSL DLLs. Windows FTS now asks for a system OpenSSL 3 runtime instead of Git Bash PATH. Co-authored-by: Cursor <cursoragent@cursor.com> * test(lbug): inject the FTS vendor root and redact it on HTTP and MCP Path-loaded artifacts no longer vary with HOME. Tests pass an injected vendor tree and assert search warnings never leak a filesystem path. Co-authored-by: Cursor <cursoragent@cursor.com> * docs(lbug): document load-only as the global FTS install default Analyze still overrides to auto. Packaged per-platform artifacts load before any network install on macOS, Windows, and Linux. Co-authored-by: Cursor <cursoragent@cursor.com> * docs(lbug): format the FTS install-policy README table Prettier does not run on Markdown in pre-commit, so the U10 table wrap needs its own formatting commit. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): skip FTS CREATE after a persisted native abort A recovered analyze run was retrying CREATE_FTS_INDEX from skipReason alone. Keep that skip until --repair-fts, fail closed on unsupported tuples, and honor the checkpoint warrant for park/repair. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): honor checkpoint flushed warrant and align FTS tests with packaged vendor A no-op CHECKPOINT must not satisfy the FTS park warrant, and CI still asserted HOME-only FTS isolation after analyze started path-LOADing the packaged artifact. Co-authored-by: Cursor <cursoragent@cursor.com> * test(lbug): accept a nonempty incremental write set in the #2790 recovery check FTS-phase recovery can incremental-add files (changed=0, added=1). That is not the #2790 empty-diff wipe skip. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): compare FTS home versions to the core pin and tighten the publish filename gate Ladybug's ~/.lbdb/extension directory is the runtime/core version; treating it as the artifact version false-diagnosed skew. The publish guard now rejects a path-escaping filename the same way the fetch script does. Co-authored-by: Cursor <cursoragent@cursor.com> * test(lbug): seed FTS e2e fixtures from the packaged vendor artifact A machine with no ~/.lbdb copy should still run the vendor-survivorship cases; the seed no longer depends on HOME or a network install. Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3274) Keep in-place FTS abort evidence after persist so a second CREATE abort cannot fail-open readers, and close the CLI, loader, embed, and e2e gaps the review called out. Note: full npm test hit Ladybug worker-pool startup failures under memory pressure; tsc and 180 targeted unit tests passed. Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3274) Run the vendored-path symlink guard on the OS matrix, put e2e HOME fixtures on Ladybug's real extension layout, pin the embed crash-WAL gate before the writable open, and let analyze writers park through missing-shadow recovery. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): keep --repair-fts CI green after vendored-first FTS Never-installed warning fixtures must not inspect a packaged vendor binary, and a failed dirty restamp must not abort an otherwise successful --repair-fts run. Co-authored-by: Cursor <cursoragent@cursor.com> * test(cli): give the #1169 analyze e2e the same 90s Windows budget as its sibling The first #1169 persist-meta case was still on a 60s spawn/it budget and was killed banner-only on windows-latest after the FTS warning fixture no longer failed the shard first. Co-authored-by: Cursor <cursoragent@cursor.com> * test(ci): reweight Windows shards after the FTS e2e grew Vendored-first HOME fixtures pushed fts-extension-e2e to ~6 minutes on windows-latest, so the old 146s weight packed it with skills-e2e and blew the 20-minute watchdog. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
c4ecf398de
|
chore: release v1.6.12 (#3272)
Some checks failed
Gitleaks / gitleaks (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Scorecard / Scorecard analysis (push) Has been cancelled
Skill copy sync / shipped skills drift guard (push) Has been cancelled
Publish / Classify release event (push) Has been cancelled
Trivy Image Scan / Trivy (gitnexus-cli) (push) Has been cancelled
Trivy Image Scan / Trivy (gitnexus-web) (push) Has been cancelled
Publish / RC guard (marker + release-PR skip) (push) Has been cancelled
Publish / Build & Push RC Docker images (push) Has been cancelled
Publish / ci (push) Has been cancelled
Publish / Publish to npm (push) Has been cancelled
|
||
|
|
79543c8f83
|
feat(storage): add configurable index storage and content retention tiers (#3060)
* feat(storage): add configurable index storage and content retention tiers Rebase #3060 onto current origin/main. Keep GITNEXUS_STORAGE_PATH, GITNEXUS_STORAGE_ROOT, and GITNEXUS_CONTENT_RETENTION, and fold in main's FTS skip, embed-session, and help-text updates. Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3060) Keep legacy registry rows on the local storage fallback, resolve symlinks before the destructive-path guard, and align hook lookup with CLI branch slugs, branch-slot metadata, and longest-path match. Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3060) Only list swept upload directories after a successful removal so callers cannot treat a permission or transient rm failure as gone. Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3060) Document that getStoragePath may consult registered storage while this module still does not mutate the global registry. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(storage): close review findings for external indexes and retention Re-inspect ownership under the analyze lock, fail-closed when the registry file is missing, and keep skip-git hook discovery plus retention fields on HTTP/MCP list surfaces. /api/file stays 410 unless contentRetention is full. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(autofix): apply prettier + eslint fixes via /autofix command * Address PR review feedback (#3060) Treat lock-only index dirs as empty, honor HTTP --force storage policy, and prefer registered plus branch-aware slots in hooks and augment. Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3060) Keep hook fallbacks inside the current worktree, compare foreign-local slots canonically, and make storage fixtures survive ownership validation. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix macOS hook test expecting realpath'd registry paths. resolveHookRepo returns the written registry path, not a filesystem realpath, so the assertion must match that. * Address gitnexus-check warnings on hook install docs and slot tests. The Cursor troubleshooting list omitted registry-query.cjs, and the writable-slot test only checked that isDirectory exists instead of that the path is a directory. * Align the HTTP catalog source-scan with skippable resolveRepo validation. resolveRepo lists fresh repos with validate: options.validateStorage !== false so DELETE can skip prune; the test still required a literal validate: true. * Harden storage path sinks so CodeQL path-injection and ReDoS alerts clear. Contain every filesystem probe inside the resolved storage slot with the inline path.relative idiom, reject filesystem-root slots, and trim slot basenames in linear time. * Settle bridge stamps before writing so CI size/mtime matches stay stable. LadybugDB can still flush into bridge.lbug after close+rename; persist whole-millisecond mtimes and wait for consecutive stats to agree so a freshly written pair matches. * Type the settled bridge stat as fs.Stats so tsc does not see bigint. Awaited<ReturnType<typeof fsp.stat>> collapsed the bigint overload and broke prepare/typecheck on CI. * Keep the bridge mtime stamp exact so same-size swaps still fail the pair check. Co-authored-by: Cursor <cursoragent@cursor.com> * Wrap the bridge stamp predicate so prettier --check stays green. Co-authored-by: Cursor <cursoragent@cursor.com> * Require a quiet interval before stamping a settled bridge file. Co-authored-by: Cursor <cursoragent@cursor.com> * Reuse shared storage and settle helpers instead of local copies. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
a8736a07d0
|
fix(lbug): checkpoint race in pool-adapter.ts + pin @ladybugdb/core to 0.18.3 (#3189)
* fix(lbug): await evict-then-reopen so it can't race the checkpoint
closeOne() closed the evicted repo's shared Database with a
fire-and-forget `db.close().catch(() => {})` (no await). Both call
sites that evict-then-reopen — evictLRU() right before doInitLbug
opens the new connection, and the "idle & changed" path in initLbug —
proceeded to open the next repo's connection immediately after,
without waiting for the evicted repo's close (and the checkpoint it
triggers) to finish. On the real engine the new open can then collide
with that still-in-flight checkpoint, surfacing on any read as:
Runtime exception: Cannot open database in read-only mode while
checkpoint is in progress. Please retry later.
This reproduces reliably once more than MAX_POOL_SIZE (5) distinct
repos are queried within a short window (self-hosted deployments with
more than a handful of active repos hit it routinely), and gets worse
under genuinely concurrent requests for different repos, since nothing
serialized pool mutations across callers either.
Fix:
- closeOne / evictLRU are now async and await their internal work
(closeOne's own close() call; evictLRU's call to closeOne), closing
the race within a single initLbug call.
- The exported initLbug is wrapped in a small async mutex
(initLbugInner does the real work) so concurrent initLbug calls for
different repos serialize instead of each racing their own
evict-then-reopen against the others.
- closeLbug's two closeOne() calls are now awaited too — closeOne
becoming async meant closeLbug could resolve before pool.delete()
had actually run, which a repo-pinning test caught (isLbugReady()
briefly still true right after a resolved closeLbug()).
- closeOne now deletes the pool entry (and clears its pin, and
notifies pool-close listeners) BEFORE the awaited db.close(), not
after. Review caught that the previous order left a "zombie" entry
reachable via pool.get(repoId) — closed=true, available emptied, but
still present — for the duration of that await; a same-repo
query/init landing in that window would see isLbugReady() as true
and hit a "Connection pool integrity error" in checkout() instead of
just reopening. Deleting first removes the entry entirely, so a
concurrent caller takes the normal fresh-open path instead.
Verified two ways:
- Against the compiled bundle (`ghcr.io/abhigyanpatwari/gitnexus`,
1.6.10/1.6.11 — pool-adapter.js is byte-identical between them): an
A/B docker build with 7 tiny local repos and genuinely concurrent
(parallel, not sequential) /api/graph requests goes from 7/7 failing
to 7/7 succeeding on a freshly-analyzed pool.
- Unit tests here (mocks @ladybugdb/core the same way as
lbug-pool-pinning.test.ts): one asserts the evicted repo's close()
completes before the initLbug call that triggered the eviction
settles; another asserts closeLbug's own promise doesn't resolve
before the underlying close() does. Both gate their mock's close()
on a real short delay and were confirmed to fail against code that
drops the corresponding await.
Note: a second, deeper issue was also observed in the docker A/B
setup — repeated rounds of concurrent access show a repo that has
gone through one evict+reopen cycle can become permanently unable to
reopen for reads, identically with and without this fix. That did not
reproduce with mocks and isn't understood yet; filed separately as
#3186, which stays open and untouched by this PR — this fix closes a
real, root-caused bug on its own but does not resolve #3186 by itself.
Second review round caught a follow-up: the idle-timeout sweep calls
closeOne(repoId) directly, outside of initLbug's poolLock. Now that
closeOne deletes the pool entry before its awaited close(), an
unsynchronized idle close racing a same-repo initLbug could let that
initLbug treat the repo as absent while the idle close (and its
checkpoint) is still in flight — reopening the same class of race this
PR exists to close, just via the idle path instead of LRU eviction.
Routed the idle sweep's closeOne call through withPoolLock too, so it
serializes against initLbug the same way evictLRU already does.
(Tried to add a mocked regression test for this specific interleaving;
dropped it — the mock's dbCache-reuse path masks the difference
regardless of the fix, so it could not be made to discriminate
reliably. Fixed by direct code review instead, same as the note below
already does for the native-engine-specific checkpoint collision.)
Also removed the initLbugInner per-repoId initPromises dedup map: with
every initLbug call now serialized through poolLock, a second call for
a repoId already being initialized cannot observe a pending promise in
initPromises (the first call always fully completes, including its
finally-block cleanup, before the lock releases) — the branch was dead
code the bot correctly flagged twice.
Third review round caught two more follow-ups on the same theme (both
introduced by making the idle sweep route through poolLock):
- closeLbug()'s no-arg ("close everything") branch still calls closeOne
directly in a loop over a snapshotted pool.keys(), without the lock —
an initLbug racing that loop could register a fresh entry the
snapshot never saw, leaving it resident after a call meant to empty
the pool. Wrapped the snapshot+loop in withPoolLock.
- The idle timer callback can now sit queued behind an in-progress
initLbug before its turn arrives, and that init (or a concurrent
touchRepo()) can refresh lastUsed in the meantime — so the pre-lock
idleness check taken when the timer fired can be stale by the time
it actually runs. Re-check lastUsed/checkedOut again inside the lock,
right before closing, instead of trusting the outer snapshot.
* fix(deps): pin @ladybugdb/core back to 0.18.3
Bisected the "checkpoint is in progress" symptom (root cause #2, not
touched by the pool-adapter.ts fix in the previous commit) down to a
single dependency-version-bump commit with zero application code
changes:
|
||
|
|
ceaff27c1e
|
fix(parse-cache): retire a chunk whose durable generation could not be reset (#3271)
* fix(parse-cache): retire a chunk whose durable generation could not be reset #3200 skipped the parse-cache write when `prepareDurableParsedFileChunk` failed, but the chunk hash was already in `usedKeys` from the lookup. When a previous generation existed on disk — reachable because the coherence gate re-dispatches a chunk whose `.v8` shard is live but whose durable shards are unreadable — `saveParseCache` copied that old shard forward, and the durable prune, which keeps exactly the saved keys, retained the mixed directory. The next run then served a warm hit out of a directory the previous run had already decided it could not account for. Retire the hash instead of only skipping the write: - `ParseCache.staleKeys` is a transient set that `saveParseCache` filters out of its key list. Filtering at save is what makes it survive the post-parse key merges in run-analyze (#2106 sibling fold, unreadable-meta retention), and it reaches both stores at once because the durable prune keeps exactly the keys `saveParseCache` returns. - The hash is retired at the reset-failure site, which runs unconditionally. The parse-cache write branch sits behind `rawResults.length > 0`, so a chunk whose worker round returns nothing would never have been retired there. - Worker-quarantined chunks get the same treatment for the same reason: they also reach the save with no in-memory entry, which is what triggers the copy-forward. That branch was previously unreachable when the worker died on the chunk and returned no results. - Guard the durable prune's non-survivor `fs.rm`. The causes that break the reset break that delete too, and it sat outside the validation try — one undeletable directory aborted the loop and cost every remaining chunk its index entry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(review): apply review findings Retire a chunk only when a generation nobody cleared is still on disk. `prepareDurableParsedFileChunk` is rm-then-mkdir, and the catch could not tell the two apart: an rm that succeeded before a failing mkdir leaves NO directory, so the workers recreate it and write a clean generation. Retiring there discarded a good `.v8` for no safety gain — and under a correlated failure (an empty durable index turns every chunk into a re-dispatched miss, then a descriptor burst rejects the resets en masse) it would have wiped both shared stores for every branch, where the pre-#3204 posture cost only the writes. `durableChunkHasStaleShards` is the discriminator. Finish the delete guard on the path that runs before it. The staged→live overlay in `mergeStagedDurableParsedFileStore` awaited `replaceDurableChunkDir` unguarded, so on the cold-rebuild path one undeletable directory threw out of the merge before the prune ever ran — the durable index was never rewritten and a retired chunk kept its directory. Same log-and-continue treatment, plus best-effort handling of the two `.replacing` backup removals. Aggregate the prune's delete-failure warning: a store-wide cause hits every non-survivor, and one line per directory buries the message that matters. Tests: - Guard the chmod-based prune test with the repo's `skipIf` for root/Windows and assert the directory survived, so it cannot pass vacuously where the delete succeeds. - Add a two-chunk control: one chunk's reset fails, and the sibling must stay warm through the next run. One chunk plus a global spawn marker could not tell "retires the failing chunk" from "retires everything". - Add the rm-succeeded/mkdir-failed case, which must NOT retire. - Model both post-parse merges in the R4 test (the sibling fold re-adds the key, the unreadable-meta fallback unions `entries`), and move the in-memory `entries` assertion to a direct helper test — the sharded path never populates `entries`, so the old assertion proved nothing. - Type the cache factory as `ParseCache`; the `staleKeys` assertions were TS2339 and `?? false` read as a pass regardless. - Register the store test in the cross-platform filesystem list. Correct two comments that still described a quarantined chunk by the premise this fix disproves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(review): stop swallowing the backup removal in replaceDurableChunkDir Swallowing that `fs.rm` manufactured the very hazard this PR removes. When a non-empty `${to}.replacing` survives, the following `fs.rename(to, backup)` cannot overwrite it and is suppressed as "dest was missing", so `backedUp` stays false and the `fs.cp(from, to)` fallback merges the staged generation INTO the live directory — old shards alongside new, which the prune then indexes as one valid survivor. Let it throw; the per-entry guard added to `mergeStagedDurableParsedFileStore` already stops one such chunk from costing the others their prune. The post-publish backup cleanup stays best-effort, where an undeletable leftover really is litter. Also: - Make the sibling-isolation test perform the run its title claims. It asserted index membership and stopped; an index entry does not exercise the warm-hit path, so it would have passed even if the sibling re-dispatched. Each chunk now runs alone so the single spawn marker names which one re-parsed. - Correct two comments that outran the implementation: retirement is gated on shards actually surviving, and an undeletable directory is dropped from the index rather than removed from disk. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3f5ca8cdb7
|
fix(storage): guard stale file-lock reclamation (#3234)
* fix(storage): guard stale file-lock reclamation * fix(storage): close lock recovery failure paths * Address PR review feedback (#3234) - Flush the lock-child stderr diagnostic before process.exit - Treat explicit NaN timeouts as the default ceiling - Document non-retryable guard timeouts on the worker IPC contract Co-authored-by: Cursor <cursoragent@cursor.com> * fix(storage): stop mislabeling live lock waits as orphan recovery A brief peer inspect must not attach guardPath or send operators to RUNBOOK delete steps. Refuse lock-free embeddings sync, stop --watch only on a true guard timeout, and drop an unreadable self-created guard before failing closed. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(autofix): apply prettier + eslint fixes via /autofix command * Address PR review feedback (#3234) - Verify lock/guard absence before degrading a denied main-lock create - Launch the third contender from unlinkSync, not the dead rename path - Document group-lock timeouts for unrecoverable guard leftovers Co-authored-by: Cursor <cursoragent@cursor.com> * style: prettier index-lock reclaim guard tests Co-authored-by: Cursor <cursoragent@cursor.com> * fix(storage): treat O_EXCL as the lock-file presence check CodeQL flagged existsSync-then-wx on analyze.lock. Create with wx first and only reclaim unreadable leftovers after grace, so a successor is never unlinked from a lost race. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
1f64becb30
|
fix(mcp): reject unknown tool arguments and honor depth (#3267) | ||
|
|
68eca0ced8
|
fix: map deleted files to indexed symbol ranges (#3269)
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
75cc8dddbc
|
chore(deps)(deps): bump ignore from 7.0.8 to 7.0.9 in /gitnexus (#3265)
Bumps [ignore](https://github.com/kaelzhang/node-ignore) from 7.0.8 to 7.0.9. - [Release notes](https://github.com/kaelzhang/node-ignore/releases) - [Commits](https://github.com/kaelzhang/node-ignore/compare/7.0.8...7.0.9) --- updated-dependencies: - dependency-name: ignore dependency-version: 7.0.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
68c42009df
|
fix(dart): anchor @name so a constructor initializer stops minting a … (#3224)
Some checks are pending
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
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 / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Skill copy sync / shipped skills drift guard (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
* fix(dart): anchor @name so a constructor initializer stops minting a phantom
A Dart declaration whose value is a constructor call parses the callee as a
SECOND (identifier) sibling of the declared name:
final TextEditingController _title = TextEditingController();
-> initialized_identifier[ identifier "_title",
identifier "TextEditingController", selector ]
The five graph-node rules that capture fields and top-level variables matched
`(identifier) @name` without the first-child anchor, so @name bound to both
siblings and the query minted a phantom Property/Variable named after the TYPE
alongside the real declaration. On dart-flutter-conduit that produced a
`Property TextEditingController` next to the genuine `_title` / `_body` in
editor_screen.dart and login_screen.dart.
static_final_declaration has the same shape, so class statics and top-level
final/const were affected too, as were top-level `var`/`final` variables. All
five rules now anchor @name with `.`, matching the mirror rules in
languages/dart/query.ts which already anchored.
Verified against the vendored grammar: the phantoms disappear and every real
declaration is still captured (_title, _body, nullable field, static final,
uninitialized field, top-level final, top-level var). End to end on
dart-flutter-conduit: Property nodes 108 -> 106, type-shaped names 2 -> 0,
real fields unchanged.
The new test loads the grammar via createParserForLanguage rather than
loadLanguage: loadLanguage resolves to void, so the surrounding
`if (!(await loadDartOrSkip())) return;` idiom is always falsy and skips the
body. Confirmed as a negative control -- reverting only the query change makes
the new test fail on the exact phantom.
* chore(autofix): apply prettier + eslint fixes via /autofix command
* Address PR review feedback (#3224)
Correct the RHS_ONLY_TYPES comments so they state the capture invariant
instead of claiming those names appear only as constructor callees.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Address PR review feedback (#3224)
Build the Dart query with Parser.Query and parser.getLanguage() so the
test no longer casts the tree (or Query/captures) through any.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
||
|
|
af2d9aec15
|
refactor(fts): share skip-FTS helpers and capability defaults (#3263)
* refactor(fts): share skip-FTS helpers and capability defaults Keep analyze, repair, and HTTP open paths on one option/capability object so the same stamp cannot drift. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(lbug): keep initLbug options as skipFts-only Restore the public initLbug and ensureFtsRowDmlSafe option shapes so one-arg callers stay on the pre-#3263 contract. Co-authored-by: Cursor <cursoragent@cursor.com> * revert(lbug): drop LbugInitOptions alias that tripped contract-drift Keep the session option objects as inline types so initLbug's public signature matches main byte-for-byte. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
f8036ac349
|
feat(analyze): add explicit FTS opt-out (#3205)
* feat(analyze): add explicit FTS opt-out * chore(autofix): apply prettier + eslint fixes via /autofix command * fix(analyze): treat flag and env FTS opt-out as one mode Avoid a same-commit rebuild when only the skipReason discriminator changes, and advertise disablement on dirty meta before leftover indexes are wiped. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
d1a3edd333
|
perf(mcp): avoid O(n) git spawns on tools/list with many repos (#3259)
* perf(mcp): avoid O(n) git spawns on tools/list with many repos toolSchemaRepoRequirements called listAllowedRepos -> listRepos -> checkStalenessAsync for every registered repo. With ~200 repos, this spawned 200 parallel git rev-list processes on every tools/list discovery call, causing a ~30s delay. Replaced with a lightweight countRepos() method that reads the registry file once without spawning git processes, preserving full staleness checks for list_repos. * Address PR review feedback (#3259) Count the validated registry in countRepos so tools/list cannot advertise a multi-repo schema for ENOENT ghosts, and update the unrestricted listTools mocks to that contract. Note: pre-existing failure in update-notice.test.ts (missing dist/cli/mcp.js) not addressed by this PR. Co-authored-by: Cursor <cursoragent@cursor.com> * Add a 200-repo tools/list bench for the countRepos path (#3259) Pin the #1363 comparison (listRepos git fan-out vs validated countRepos / listTools) in-tree so the latency claim can be re-run. Also drop the change-history comments on the unrestricted schema arm. Co-authored-by: Cursor <cursoragent@cursor.com> * Gate the tools/list bench with baselines and CI --check (#3259) Exact registry/schema floors plus ratio timing, no millisecond ceiling, so restoring listRepos() on tools/list fails CI. Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3259) Align unrestricted tools/list schema flags with the refreshed registry snapshot, and make the bench reject a non-positive BENCH_REPS and isolate fixtures by N. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(autofix): apply prettier + eslint fixes via /autofix command * Address PR review feedback (#3259) Isolate the tools/list bench from GITNEXUS_MCP_READ_ONLY and create the default fixture under mkdtempSync so CodeQL is not looking at a predictable /tmp path. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
7bbaf6b73b
|
fix(review): fail closed on foreign embedding identity and vector-width drift (#3260)
Applied from a ce-code-review pass over this branch (run 20260911-053832-b49f88d6). - `embeddings sync` now gates EVERY checkpoint kind on embedding identity. `unverified-count` was exempted, but `decideEmbeddingResume` abandons that kind before it compares identity, so the exemption was the only thing keeping a foreign model from filling holes beside the old model's vectors — two vector spaces in one CodeEmbedding table, reported healthy. The test that asserted this run succeeds now asserts the refusal. - `embeddings sync` refuses when the index's recorded vector width differs from this run's. The column is FLOAT[N] fixed at build time and the pipeline deletes each batch's stale rows immediately before inserting, so a width change deleted rows it could not re-insert. `analyze` already forces a rebuild on the same mismatch; only a rebuild can retype the column. - `GITNEXUS_EMBEDDING_RETRY_TIMEOUTS` parses with the repo's truthy convention (`1`/`true`/`yes`). The integer parser threw on `true`, so the conventional spelling hard-failed every embedding call rather than enabling the flag or leaving it off. README names the accepted spellings. - One `persistMeta` write path replaces three inlined metadata read-modify-writes; #2790 traced two production drifts to hand-copied writers of these exact fields. - Tests: the pipeline mock now invokes `onCheckpointWindowStart`/`onCheckpoint`, so the resume contract actually executes under test. Added coverage for the incomplete-index refusal, the partial-checkpoint branch, and the body-read timeout retry site that the existing opt-in test never reached. Verified: tsc --noEmit clean; 99 tests pass across the three affected suites; prettier clean. Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
8bd71c8335
|
feat(staleness): report diverged and unknown index state instead of fresh (#3257)
* feat(staleness): report diverged and unknown index state instead of fresh
A staleness check collapsed every git failure into { isStale: false,
commitsBehind: 0 }. On a branch-pinned serve clone, a failed re-index
leaves the recorded commit orphaned by the --depth 1 fetch; once the
reflog expires and gc prunes it, rev-list fails and the index silently
reads as fresh while still behind.
checkStaleness / checkStalenessAsync now return an additive status:
current, behind, diverged (rev-list failed but HEAD resolved and differs
from lastCommit) or unknown (HEAD unresolvable, no lastCommit, timeout).
isStale and commitsBehind keep their values in every case.
One payload builder (core/staleness-status.ts) feeds MCP list_repos, the
hot read tools and /api/repos + /api/repo. diverged carries a hint and no
invented count; unknown appears on listings only. The helpers live in a
pure module so existing vi.mock stubs of git-staleness stay valid.
gitnexus group status no longer prints "-1 commits behind".
Fixes #3256
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Address PR review feedback (#3257)
- Document the `current` arm `fromHead` can return after a failed `rev-list`.
The `staleness-status.ts` status list, the `fromHead` docstring and the
`stalenessForTool` comment each named only `diverged`/`unknown`, so all three
described a contract the helpers do not have.
- Document both `unknown` rows on the group status type: no recorded commit
(`indexStale: true`, `commitsBehind: -1`) and a git probe that could not
answer (`indexStale: false`, `commitsBehind: 0`), which do not agree on
either field.
- Update both shipped `gitnexus-guide` copies to the new wire shape
(`{ status, commitsBehind?, hint? }`), add a `diverged` example carrying no
count, and state `unknown` is reported only by the `list_repos` listing.
Presence now means "not `current`", not "behind N". Pinned in
shipped-skills-sync.
- Lock the timed-out `rev-list` short-circuit with staleness-timeout.test.ts:
it asserts `unknown` from exactly one git spawn, so removing the `killed`
guard (which would probe HEAD again and double the #3232 bound) now fails.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Claim only the uncountable gap in the diverged hint (#3257)
`fromHead` reaches the `diverged` branch on any non-timeout `rev-list` failure
where `rev-parse HEAD` resolves to a different SHA. It compares the two SHAs and
runs no reachability check, so a transient object-read failure lands there while
`lastCommit` is still an ancestor of HEAD — and the hint told the operator the
commit was gone from history. `staleness-status.ts` already documents `diverged`
as only "provably not at HEAD; the count is unknown", so the string was also the
one place contradicting its own contract.
The hint now states what the check established and hedges the usual cause:
"Index is not at HEAD and the commit gap could not be counted — the recorded
commit may no longer be in this clone's history."
Updated with it: the `staleness.test.ts` diverged assertion, and the `diverged`
example plus its lead-in sentence in both shipped `gitnexus-guide` copies (still
byte-identical). `mcp/resources.ts` needs no change — it interpolates
`staleness.hint`, holding no copy of the text.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(staleness): cover the remaining #3256 review test gaps
The tri-review's lower-priority gaps, each now locked:
- staleness-fallback.test.ts: after a failed (not timed-out) rev-list,
both helpers report `current` when HEAD alone still resolves to the
indexed commit, `diverged` when it resolves elsewhere, and `unknown`
when it cannot be read, each from exactly rev-list + rev-parse. This
complements staleness-timeout.test.ts: a timeout spawns once, any other
failure probes HEAD.
- list_repos carries the listing-only `unknown` and a count-free
`diverged`; `current` carries nothing.
- group status: a resolvable repo with no recorded commit composes to
indexStale: true, commitsBehind: -1, status: unknown and renders as
"STALE (? commits behind)", through the real groupStatus path rather
than a hand-built row.
Each test was checked against a mutation of the code it guards (the
fromHead current arm, the group no-commit literal, list_repos
includeUnknown); every mutation fails its test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: tech-admin3 <tech-admin@kodnest.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
|
||
|
|
c9b391357e
|
fix(group): detect function-local Python imports (#3254)
* fix(group): detect function-local Python imports * fix(group): parse Python imports structurally * fix(group): use safe Python parser wrapper * fix(group): isolate Python parse timeouts * fix(group): skip recovered indented Python from-imports Error-recovered trees can promote unclosed-docstring lookalikes into real import_from_statement nodes. Drop those indented imports, bound file reads, log parse timeouts, and add fingerprint floors for the tree-sitter scan. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(autofix): apply prettier + eslint fixes via /autofix command --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
32a5fc3c4f
|
feat: Make long HTTP embedding jobs resumable (#3065)
Some checks are pending
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
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
* Make long HTTP embedding jobs resumable Retry endpoint timeouts when explicitly configured, creating a fresh abort signal for every attempt. Add gitnexus embeddings to fill an existing index in place: every successful batch is durable, and rerunning skips vectors whose content hash still matches instead of rebuilding the structural graph. * Address PR review feedback (#3065) Hold the per-index lock around embeddings sync, fail closed on a foreign-identity partial checkpoint, and refuse to create an empty DB from leftover metadata. Use the canonical embedding count, keep closeLbug from masking the real error, load hashes instead of full vectors, and document GITNEXUS_EMBEDDING_RETRY_TIMEOUTS. Co-authored-by: Cursor <cursoragent@cursor.com> * Split embeddings sync off the install module so install no longer loads Ladybug. Share the opt-in TimeoutError wrap between fetch-throw and .json(), and cover a non-file LadybugDB path. Co-authored-by: Cursor <cursoragent@cursor.com> * Describe embeddings sync checkpoints as periodic and report cached node count. Pipeline checkpoints every 5,000 nodes, and fetchExistingEmbeddingHashes is keyed by nodeId, not vector rows. Co-authored-by: Cursor <cursoragent@cursor.com> * Leave an unverified-count checkpoint when embeddings sync cannot count rows. A finished run must not keep an interrupted window marker; the next sync should re-derive the count instead of hitting the identity gate. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(autofix): apply prettier + eslint fixes via /autofix command --------- Co-authored-by: panbergco <panbergco@users.noreply.github.com> Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
a4769439dd
|
fix(parse): retain metadata-only diff files (#3251)
* fix(parse): retain metadata-only diff files * Address PR review feedback (#3251) Keep detect_changes honest on C-quoted, TAB-terminated, and ambiguous diff --git dests, and fail closed when a header cannot be parsed. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify parseDiffHunks dest-prefix helper (#3251) Drop the unused a/ prefix arm and the redundant empty-parse unparsed-header check. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(parse): ignore +++ inside hunks and accept mixed-quoted git headers Stop treating hunk-body lines as file headers, and parse independently C-quoted src/dest tokens on diff --git. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: ming <silverchris@foxmail.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
0edf9ce0ff
|
fix(ruby): guard gem requires with dependency metadata (#3096)
* docs(plans): add ruby gem require boundary plan * fix(ruby): guard gem requires with dependency metadata * fix(ruby): scope gem sources by manifest * test(ruby): model resolved lockfile specs * fix(ruby): stop local gem suffix fallthrough * docs: remove Ruby resolution plan * test(ruby): gate gem resolution correctness and scaling * test(ruby): align gem benchmark baseline with ratio gates * Address PR review feedback (#3096) - Strip a trailing .rb so local and external gem prefix matching follows Ruby's optional-suffix require. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
e7141ab0c1
|
fix(schema): persist Spring constructor-to-bean injection edges (#3239)
Co-authored-by: Jayson Albert <momeijw@gamil.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
3236e2fbcd
|
fix(cobol): prefer copybook dirs so COPY EXTERNAL does not hit vendor decoys (#3240)
* fix(cobol): prefer copybook dirs so COPY EXTERNAL does not hit vendor decoys
COPY of an out-of-repo member first-won any same-named .cpy, so
vendor/EXTERNAL.cpy became a live cobol-copy IMPORTS edge. Share one
resolver between census and the regex processor: prefer copybooks/cpy/copy
plus the importer dir when present, else fail-open. Drop COBOL KNOWN_GAPS.
Fixes #2967
* bench(cobol): update depth budget for copybook-dir preference (#2967)
COBOL resolver now prefers well-known copybook directories (copybooks/,
cpy/, copy/, plus importer dir) over vendor paths when resolving COPY
statements. This intentional behavior change moves the resolver from
depth-free (prior measured ~0.885) to depth-sensitive (measured 1.751
on CI run 34394116972), because the new preferredCopybookDirs check
walks path components.
- Raise depth_budget from 1.6 to 2.4 (~1.37x the measured ratio)
- Update _measured.depth_ratio from 0.885 to 1.751
- Add _cobol_copybook_dir_preference_2967 note documenting the change
The COBOL fingerprints already reflect the new target set behavior
(vendor/EXTERNAL.cpy correctly returns null when a copybook dir is
present) per commit
|
||
|
|
16307a11be
|
fix(flows): exclude guessed call edges from derived graph flows (#3193) | ||
|
|
420306233b
|
chore(deps)(deps-dev): bump @testing-library/user-event in /gitnexus-web (#3246)
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 14.6.6 to 14.6.7. - [Release notes](https://github.com/testing-library/user-event/releases) - [Changelog](https://github.com/testing-library/user-event/blob/main/CHANGELOG.md) - [Commits](https://github.com/testing-library/user-event/compare/v14.6.6...v14.6.7) --- updated-dependencies: - dependency-name: "@testing-library/user-event" dependency-version: 14.6.7 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
79f210c5b5
|
fix(go, workspace): resolve test siblings and tighten package discovery (#3191)
* fix(go): resolve test helpers through package sibling tables * fix(workspace): discover source entries from scoped static configuration * Address PR review feedback (#3191) - Align sibling comments with the no-bare-name partition and drop the stale same-dir fallback claim. - Pin `_test.go` dot-import wildcard augmentation so a revert to nonTestFiles cannot stay green. Note: pre-existing failure in worker-pool startup crashes in the full vitest suite not addressed by this PR. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: tighten workspace discovery and index Go sibling bindings Skip leftover test/ workspace roots and extra Vite configs. Publish same-package Go names from per-package indexes instead of pairing every file. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
2220f4d851
|
fix(resolution): label fallback guesses and preserve export visibility (#3190)
* fix(resolution): distinguish name guesses and preserve export visibility * test(go): keep method enrichment fixture in one package * fix(resolution): address split review edge cases and evidence reporting * fix(exports): recognize imported and expression-local receivers * fix(resolution): align export and target evidence with language scope * fix(ingestion): preserve lexical import provenance through resolution * test(ci): rebalance Windows shards from measured slow suites * Address PR review feedback (#3190) - Label constructor unique-name guesses as global-name-fallback and run language vetoes - Tighten Go qualified, Rust crate::, and Ruby class-reopen fallback guards - Ignore for-loop shadowed CommonJS receivers and exclude guesses from the resolved-call census - Refresh FinalizeOutput and hook docs for lexical binding scopes Co-authored-by: Cursor <cursoragent@cursor.com> * Tighten review-feedback leftovers on fallback visibility. Qualified Go calls still respect export and test-package rules, nested Rust src/ stays a module segment, and top-level conditional this.x is treated as CommonJS. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(autofix): apply prettier + eslint fixes via /autofix command * Address PR review feedback (#3190) - Distinguish Swift package prefixes when comparing target modules - Document lexical import binding and handledSites refusal marking - Drop the stale ci-scope-parity workflow claim and prototype-safe export verdicts Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3190) Supply caller source on Ruby visibility cases so they exercise the named allow branches instead of the missing-text bypass. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(resolution): label unique constructor types as name guesses A workspace-unique class hit in findClassBindingInScope was treated as an in-scope bind, so Go/JS constructor-form sites skipped the guess label and the Go unexported veto. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(resolution): keep qualified constructors precise after unique-name split Bare constructor unique-name hits stay guesses so Go can veto an unexported type. A written qualifier is now carried as rawQualifiedName so `new pkg.Foo()` and `models.Box[T]{}` can still recover the unique class without that veto. Co-authored-by: Cursor <cursoragent@cursor.com> * test(bench): rebaseline Go/Java scope-capture fingerprints for constructor qualifiers Generic Go composite literals and qualified Java `new pkg.Foo()` now carry @reference.qualified-name on existing constructor matches. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(resolution): treat import-reached unique constructors as precise C++ #include and Rust re-exports do not mint a lexical class binding. A unique type in an imported file (or imported directory) is therefore a real bind, not a name guess, so those CALLS edges stay import-resolved. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(resolution): require named or resolved imports for constructor precision Bare Go Box[T]{} is not package-qualified, and a sibling-file import of a different name is not constructor visibility. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
1909af4e04
|
chore(deps): bump softprops/action-gh-release from 3.0.2 to 3.0.3 (#3248)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 3.0.2 to 3.0.3.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](
|
||
|
|
579a438761
|
chore(deps)(deps-dev): bump @types/react in /gitnexus-web (#3245)
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 19.2.14 to 19.2.18. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) --- updated-dependencies: - dependency-name: "@types/react" dependency-version: 19.2.18 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
ab25b86807
|
fix(typescript): resolve tsconfig paths aliases on Windows (#3203)
* fix(typescript): resolve tsconfig `paths` aliases on Windows `rebaseTarget` turns a `paths` target that `path.resolve` produced back into a repo-relative one, and it recognised the wildcard suffix only as `/*`. On Windows the resolved target is `C:\repo\src\*`, so the check never matched, the bare-`*` branch stripped the star, `path.relative` ate the trailing backslash, and every alias target came back as `src*`. `substituteStar` then built `srclib/date` from `@/lib/date`, nothing resolved, and every alias import was dropped as external — no IMPORTS/CALLS edge for anything reached through `@/`, so `impact` answered UNKNOWN for a repo's whole shared layer. Normalise the separator before looking at the suffix. `tsconfig-index.test.ts` already asserts the `src/*` shape and fails 4 of 13 cases on Windows without this; it only ever ran on Ubuntu, so add it to the cross-platform lane. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(typescript): make `paths` rebasing assertable from any runner `rebaseTarget` reads the platform separator, but the only suite covering it builds real directories — so on Ubuntu it sees `/` whatever the code does with `\`, and only the windows-latest lane can fail it. That is how an alias bug affecting every Windows install reached a green CI in the first place. Thread an injectable `pathApi` through `rebaseTarget` and `repoRelative` — the seam `isInside` and the `\\?\` prefix guard already use — and add a fixture-free suite that pins the win32 and POSIX branches explicitly. Deleting the separator normalisation now fails on every runner rather than only on windows-latest. Registered beside its fixture sibling in the cross-platform lane so the two halves of the same rule stay discoverable as one group. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(typescript): emit a bare `*` for a repo-root `paths` target A target naming the repo ROOT — `"*": ["./*"]` under `"baseUrl": "."` — rebases to an EMPTY repo-relative prefix, so only the suffix survived and the encoding came out as `/*`. `substituteStar` turns that into `/lib/date`, and `resolveFile` matches repo-relative keys without stripping a leading slash, so the indexed `lib/date.ts` misses and the alias is dropped as external. Emit the bare `*` for that case instead, which substitutes to `lib/date` and resolves. This is the shipped POSIX encoding as much as the Windows one, and it is what the Windows path emitted by accident before the separator was normalised — so the normalisation does not narrow what already resolved there. The common `"@/*": ["./src/*"]` is untouched: it has a real prefix and stays `src/*`. Pinned at all three levels the encoding passes through: the rebaser, on both path flavours; the loader, through a real fixture; and resolution, where `*` finds the file and `/*` does not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(test): say why the root-wildcard fixture declares no `baseUrl` The comment introduced the fixture as `"baseUrl": "."`, which the loader encodes as `''`, while the fixture passes `null` — a different scope, since `null` means the config declares no baseUrl at all and takes the paths arm alone. `null` is the right fixture and the comment was the wrong half: with `''` the baseUrl arm resolves `packages/utils/src/index` by itself, so the negative case would return null for a reason unrelated to the target encoding. Verified by substitution — `''` makes that assertion report the file instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
2f7e192add
|
Remove cryptocurrency notice from README
Removed important notice about GitNexus cryptocurrency. |
||
|
|
d80230a564
|
chore(deps): bump docker/setup-qemu-action from 4.2.0 to 4.3.0 (#3249)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](
|
||
|
|
565cc22d18
|
chore(deps)(deps-dev): bump @vitejs/plugin-react in /gitnexus-web (#3247)
Bumps [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) from 6.0.5 to 6.1.1. - [Release notes](https://github.com/vitejs/vite-plugin-react/releases) - [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.1.1/packages/plugin-react) --- updated-dependencies: - dependency-name: "@vitejs/plugin-react" dependency-version: 6.1.1 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
43d1d3e35b
|
chore(deps)(deps): bump @langchain/langgraph in /gitnexus-web (#3244)
Bumps [@langchain/langgraph](https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core) from 1.4.9 to 1.4.14. - [Release notes](https://github.com/langchain-ai/langgraphjs/releases) - [Changelog](https://github.com/langchain-ai/langgraphjs/blob/main/libs/langgraph-core/CHANGELOG.md) - [Commits](https://github.com/langchain-ai/langgraphjs/commits/@langchain/langgraph@1.4.14/libs/langgraph-core) --- updated-dependencies: - dependency-name: "@langchain/langgraph" dependency-version: 1.4.14 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
eafcf82a14
|
chore(deps)(deps): bump react-i18next in /gitnexus-web (#3243)
Bumps [react-i18next](https://github.com/i18next/react-i18next) from 17.0.12 to 17.0.13. - [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md) - [Commits](https://github.com/i18next/react-i18next/compare/v17.0.12...v17.0.13) --- updated-dependencies: - dependency-name: react-i18next dependency-version: 17.0.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
506432017f
|
fix(zig): model callable-value references, and stop reporting their absence as exact (#3219)
Some checks are pending
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
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
Scorecard / Scorecard analysis (push) Waiting to run
* fix(impact): stop reporting 'exact' over unmodelled callable-value references
A function named in VALUE position — `bridge.accessor(Element.getNamespaceUri,
null, .{})`, `{ onClick: handler }`, a comparator handed to a sort — is
registered somewhere rather than called. The registration is modelled (a
`value-ref` site becomes a USES edge; Kythe `ref` vs `ref/call`, Joern
METHOD_REF), but the invocation THROUGH the stored value is not: it happens
later via a struct field, a registry lookup or comptime reflection.
impact()/context() nonetheless reported such a target as `epistemic: 'exact'`.
For lightpanda-io/browser that meant the DOM `Element.namespaceURI` accessor
came back with two internal callers, LOW risk and a claim of completeness —
worse than no answer, because 'exact' tells the reader not to look further.
tools.ts defines 'lower-bound' as "the walk provably missed callers", which is
precisely this case.
computeEpistemicBoundary now probes for inbound USES edges stamped with the
value-ref reason and hedges when it finds any, contributing a boundary note and
a new `causes.callableValueReferences` (unit: distinct referrer symbols).
Read from the graph, not from index metadata: unlike a dropped receiver — which
leaves no edge to find and therefore needs a persisted summary — a value
reference IS in the graph. So the signal needs no re-index and no analyzer
change, and it works on indexes written before this commit.
The cause gets its own slot rather than joining `dispatchBoundary`: a value
referrer is neither an implementation nor an interface-level consumer, and the
two differ in what the reader should do about them — a dispatch boundary is
irreducible, a callable value usually becomes traceable once the provider
models the store/load that carries it.
Language-neutral: every provider that emits a `value-ref` capture participates.
The writer and the reader now share VALUE_REF_EDGE_REASON, because drift
between them would fail silently — the probe would match nothing and every
answer would go back to claiming certainty.
* fix(zig): emit `value-ref` for a callable named in value position
`mapReferenceKindToEdgeType` has handled the registration-vs-invocation case
since #2437, and TypeScript, JavaScript and C++ all emit `value-ref`. Zig
emitted zero — so a Zig function handed somewhere as a VALUE was absent from
the graph entirely.
That is not a corner: Zig's JS bridge is built out of this one shape,
pub const namespaceURI = bridge.accessor(Element.getNamespaceUri, null, .{});
and `bridge.{accessor,function,indexed,…}` appears 2,047 times across 257 files
in lightpanda-io/browser — the project's whole JS<->Zig surface, none of it
reaching the graph. `impact` on `Element.getNamespaceUri`, which IS the DOM
`Element.namespaceURI` accessor, answered with its two in-file callers.
Three query rules, tagging `@reference.value-ref` on: a bare identifier in
argument position (`bridge.accessor(_tagName, …)`), a qualified one
(`bridge.accessor(Element.getNamespaceUri, …)`), and a const binding initialiser
(`pub const defaultHandler = onReset;`). Everything downstream already existed —
no new edge type, no schema change, no capture-machinery change, no baseline
edited.
One grammar detail is load-bearing: in tree-sitter-zig, call arguments are
DIRECT children of `call_expression` — there is no `arguments` node, only
builtins have one — so the callee has to be consumed explicitly by `function:`.
Without that binding the same rule also matches the callee of `foo(bar)` and
mints a USES edge shadowing the call's own CALLS edge. There is a test for it.
The rules are deliberately broad — `js.Bridge(Element)` and `register(count)`
match too — because the callable gate in the property-dispatch pass
(Function/Method/Constructor only) is the filter, the same design that keeps
TypeScript's `{ port: DEFAULT_PORT }` from registering anything. Measured on the
real corpus: all 3,169 emitted edges land on a Method (3,146) or Function (23).
Deliberately NOT modelled: the terminal invoke. The value reaches
`Accessor.init` -> a struct field -> `Factory.zig` reflection (`inline for`,
`@typeInfo`) -> `Caller.zig`'s `@call(.auto, func, args)` over `func: anytype`.
Resolving that needs comptime evaluation. The point is to stop dropping the
reference; the shortfall is now reported as `epistemic: "lower-bound"` by the
preceding commit instead of being papered over.
Measured on lightpanda-io/browser (698 .zig files), wiped index both times:
30,222 nodes / 71,070 edges -> 30,222 nodes / 74,229 edges. The delta is
entirely `USES` (3,169 after vs 10 before, and every one is a value-ref); CALLS,
ACCESSES, IMPORTS, HAS_METHOD, DEFINES and the rest are unchanged — purely
additive, no node invented.
The fixture joins the existing `zig-idioms` corpus rather than adding a new
one, because `bench/receiver-resolution` uses `test/fixtures/lang-resolution` as
its `--check` corpus. That gate, `scope-capture` (15 languages),
`zig-cross-file-resolution` and every other bench `--check` pass unchanged.
* fix(review): resolve qualified value references through their owner, and stop
hedging on registrations the analyzer already followed
Five findings from the review bot on #3219; four valid, all addressed.
1. QUALIFIED VALUE REFERENCES RESOLVED BY TAIL NAME (the serious one).
`bridge.accessor(Element.getNamespaceUri, …)` was resolved with
`findCallableBindingInScope(site.inScope, site.name, …)`, which never sees
the receiver and gives LOCAL bindings precedence. Reproduced:
const Element = @This();
pub fn getThing(...) // main.getThing
pub const JsApi = struct {
fn getThing(...) // JsApi.getThing
pub const thing = bridge.accessor(Element.getThing, null, .{});
};
emitted `USES JsApi → JsApi.getThing` — a WRONG edge, which is worse than
the missing edge this PR set out to fix.
`resolveValueRefTarget` now resolves a site carrying an explicit receiver
through `findClassBindingInScope` → `findOwnedMember` (the machinery
`receiver-bound-calls` already uses), gated on `CALL_TARGET_TYPES` because
`findOwnedMember` also answers with fields. A bare site keeps the lexical
walk, which is what an unqualified name means. When the owner cannot be
resolved the site is DECLINED rather than falling back: declining costs a
reference, falling back mints a confident edge to the wrong function, and
the missing reference is now reported as `lower-bound` anyway.
Cost on lightpanda-io/browser: value-ref edges 3,169 → 2,799 (−12%). Those
370 were tail-name coincidences, not registrations — all 94 `Element.zig`
`JsApi` entries survive, the cross-container case resolves and is correctly
attributed (`IntersectionObserverEntry.JsApi` → `IntersectionObserverEntry.
getTarget#0`, not the enclosing observer), and both acceptance probes are
unchanged: `getNamespaceUri` 7/3/LOW/lower-bound, `getTagNameLower`
31/10/HIGH/exact.
2. A FAILED PROBE READ AS "NO BOUNDARY". `.catch(() => [])` turned an
unanswerable query into count 0 and no note, so `exact` could be published
on the strength of a question that was never asked. It now returns `null`
and emits a boundary note. This file's own `loadMeta` comment states the
rule: a probe failing must never read as certainty.
3. NOT EVERY VALUE REFERENCE IS AN UNMODELLED INVOCATION. Where
`emitPropertyDispatchCalls` sweep 2 synthesized the dispatch (reason
`property-dispatch`), the walk did NOT provably miss the caller, so hedging
was noise over an answer that was computed — and a signal that fires on
every JS/TS hook table stops carrying information. A second probe excludes
those targets. Zig never sets a property key, so the motivating case is
untouched. The exclusion is symbol-level, not per-edge, because the graph
does not record which registration produced which synthesized call; that
residual is documented at the call site.
4. `LIMIT 50` SILENTLY UNDERSTATED THE COUNT. `rows.length` over a capped row
set published a ceiling as the documented symbol count. Replaced with
`COUNT(DISTINCT other.id)` — bounded work without a bounded answer, the
shape `countByType` in the same file already uses.
5. THE TEST DID NOT PIN THE CALLER COUNT its own comment promised. Added
`expect(result.impactedCount).toBe(2)`.
New regression tests: qualified references bind the written owner and not the
nearer lexical match; an unresolvable receiver emits nothing rather than a
wrong edge; a dispatch-modelled registration stays `exact`; a probe that cannot
run hedges instead of claiming certainty.
Known limitation, pinned by a test rather than left implicit: when a `@This()`
alias's NAME differs from its container's (`const Element = @This();` inside
`main.zig`), the receiver does not resolve and the reference is declined. The
provider has `rewriteZigThisAlias` for this, but it is applied to type nodes
and extending it to reference receivers would change existing CALL-site
behaviour. Lightpanda's `const Foo = @This();` in `Foo.zig` convention makes
the names coincide, which is why the corpus is unaffected.
* fix(review): bump the parse-cache schema and resolve module-owned value references
Review round 2 on #3219. Four inline items, all reproduced against the
worktree before deciding.
P1 — the new captures could stay INERT on a warm parse cache. Adding
`@reference.value-ref` rules to `ZIG_SCOPE_QUERY` changes
`ParsedFile.referenceSites`, which is a PARSE-TIME fact, but `SCHEMA_BUMP`
stayed at 93. A repo indexed before this branch and re-analyzed after it
replays the old, empty site list for every unchanged `.zig` file — `--force`
included, since shards are content-addressed — so no USES edge is emitted, the
boundary probe measures a real zero, and `impact` on a registered accessor goes
back to `epistemic: "exact"`. #3399 un-fixed on the incremental path most users
are on, with every cold-run test still green. DECISIONS D1-1's "zero changes to
… the schema" conflated the graph schema with the cache schema.
Bumped 93 -> 98, not 94: #3190 claims 94 and #3179 claims 94 through 97 in one
PR. `incremental-parse-cache.test.ts` re-pinned, with 93-97 added to the taken
list. Re-check against origin/main and open PRs immediately before merging.
P2 — a qualified value reference through a MODULE was declined with no hedge.
R1-2 resolves a written receiver with `findClassBindingInScope`, which requires
`isClassLike`; a namespace-only `@import` handle is not class-like, so
const dom_utils = @import("dom_utils.zig"); // no `@This()` in that file
pub const comparator = bridge.accessor(dom_utils.compare, null, .{});
resolved to nothing. That is not the conservative half of R1-2's trade-off: a
declined site emits NO edge, so there is nothing for the probe to read and
`impact` on `compare` reports `exact`. Silence, not a hedge — and the pass
comment claiming otherwise was wrong on this path.
`resolveValueRefTarget` now tries the second kind of owner a qualified name can
have. `findNamespaceValueRefTarget` reads the file's `namespace` import edges
for the handle and the target module's own `origin: 'local'` module-scope
bindings for the member — the same channel `receiver-bound-calls` Case 1
already trusts for `dom_utils.compare()`, with Case 1's three guards for Case
1's reasons: `isNamespaceNameShadowed`, local-origin bindings only, and two
distinct defs under one name resolve nothing. `CALL_TARGET_TYPES` gates module
owners exactly as it gates container owners. Language-neutral: it reads generic
namespace import edges, names no language.
Still declined, deliberately: a receiver this index knows under no name at all
(the `@This()`-alias case, owners outside the workspace). There the alternative
is a confident edge to a lexically-nearer function the source did not name.
P2 — `impact-callable-value-references.test.ts` was in neither vitest list.
It opens a real engine via `withTestLbugDB(poolAdapter: true)`, so TESTING.md
puts it in the `lbug-db` include list and the `default` exclude list; it was in
neither, so `default` also collected it into the parallel pool. Added next to
its `impact-epistemic-lower-bound` sibling in both arrays.
P3 — DECISIONS.md was stale against HEAD and embedded host paths. D2-3 still
described the `LIMIT 50` that R1-5 removed and D1-3 still described the
receiver-blind resolution that R1-2 replaced; both now carry explicit
"superseded by" pointers. The `~/code/...` and mise-node paths are replaced
with placeholders.
Two smaller corrections the new cause made necessary:
- `formatImpactResult`'s `lower-bound` header hard-coded "callers binding via
DI / dynamic dispatch", which now contradicts the value-reference bullet
printed directly under it. The bullets carry the cause; the header only
states that the count is a floor.
- `tools.ts` said a `causes.callableValueReferences` of 0 means "nothing was
missed". The dispatch exclusion is symbol-level, not edge-level, so a target
with both a followed registration and an unfollowed escape also reads 0. The
docs now say a 0 means "no unfollowed registration was proven".
Fixture: `src/webapi/dom_utils.zig` (namespace-only) plus three cases in
`Element.zig` — the module-qualified registration, a non-callable module member,
and a `u8` parameter shadowing the handle. The shadow case was verified to FAIL
with the guard disabled, so it is not passing for an unrelated reason.
Gates: `tsc --noEmit` clean, `npm run build` clean, prettier clean.
`resolvers/` 3,630 passed / 3 skipped; `unit/scope-resolution` 2,010 passed;
`impact-callable-value-references` 7 passed under `lbug-db`;
`incremental-parse-cache` 40 passed; eval formatters 104 passed. Bench --check
all PASS with no baseline edited: receiver-resolution, zig-cross-file-resolution,
scope-emission, callable-value-flow, scope-capture (15 languages), python-scope.
* fix(review): guard the class receiver against a shadowing binding, and pin the dispatchability partition
Review round 3 (`gitnexus-check` bot on `cf53bbaa`). Three findings, each
reproduced against the code before deciding.
R3-1 (Error, valid, REPRODUCED) — a CLASS receiver could resolve through a
shadowing value binding. `findClassBindingInScope` is a class-only walk: it
filters the scope chain by `isClassLike`, so it steps over a nearer binding that
is a value and keeps climbing — and past the chain entirely, into a
qualified-name fallback that answers with the unique workspace definition of the
name. A `u8` parameter named `Ticker`, in a file that neither declares nor
imports the `Ticker` container another file defines, therefore emitted
`register(Ticker.fire)` as a confident USES edge to that container's method.
That is exactly the wrong-edge failure R1-2 exists to prevent, arriving through
the class channel instead of the lexical one.
Fixed with `isOwnerNameShadowedBySomethingElse` — a sibling of
`isNamespaceNameShadowed` with one extra clause. The plain namespace guard could
NOT be reused: a container is often its own local declaration
(`fn make() { const Local = struct {…}; register(Local.go); }`), and reading that
binding as its own shadow suppresses precisely the resolutions this path exists
to make — the #2723 mistake, one channel over. So a scope that binds the name
answers immediately, and the answer is "not shadowed" only when one of that
scope's own bindings IS the def just resolved.
Both halves are pinned and both were verified to fail when the guard is
weakened: the parameter case fails with no guard at all, the local-container
case fails with the plain `isNamespaceNameShadowed`.
R3-2 (Warning; mechanism correct, unreachable today; fragility fixed instead) —
the dispatch exclusion could suppress an unfollowed registration. The bot is
right about the code: sweep 2 synthesizes CALLS only for a registration whose
site carried a `propertyKey`, while the exclusion zeroes the note on ANY inbound
`property-dispatch` CALLS edge. It is not reachable in the current rule set, and
the reason is measured rather than assumed: `@reference.value-ref` is emitted by
exactly three languages — JavaScript (2 rules), TypeScript (2), Zig (3) — every
JS/TS rule also captures `@reference.property-key` (both are object-literal
shapes) and no Zig rule does. A dispatchable registration is therefore always a
JS/TS one, an undispatchable one always a Zig one, and they cannot meet on one
symbol.
Rejected: splitting the edge `reason` into dispatchable / undispatchable. It is
the precise fix, but it is a graph-content change that churns whichever side
keeps the old literal — the Zig, TypeScript and probe suites all pin
'scope-resolution: value-ref' by hand as a drift canary — and it buys nothing
against a case no rule can produce.
What was actually wrong is that the exclusion's soundness rested on a
coincidence recorded nowhere, in files nobody reading `local-backend.ts` would
open. Fixed at both ends: the exclusion site now states the invariant, the three
facts it rests on and the two options for when it breaks; and
`value-ref-dispatchability.test.ts` fails the day it does — a JS/TS rule for a
bare callback argument, a Zig rule that grows a key, or a fourth language
emitting `value-ref` at all. Verified to fire (adding a property key to a Zig
value-ref rule fails the Zig case), and its rule splitter has its own guard test
so the suite cannot pass vacuously. `ZIG_SCOPE_QUERY` is exported for that test
only.
R3-3 (Nit, valid) — a test comment claimed the wrong epistemic result. The
`declines a qualified reference whose receiver cannot be resolved` case said the
shortfall shows up as `lower-bound`. It does not: with no edge there is no
evidence and the target stays `exact`. The pass docstring was corrected in round
2 and this comment was missed. It now says the decline costs the reference AND
the hedge, and why that is still the right trade.
Gates: tsc --noEmit clean, npm run build clean, prettier clean.
`test/integration/resolvers` 3,632 passed / 3 skipped (70 files);
`test/unit/scope-resolution` 2,015 passed (120 files);
`impact-callable-value-references` 7 passed under `lbug-db`. Bench --check:
receiver-resolution, zig-cross-file-resolution, scope-capture (15 languages),
scope-emission PASS with no baseline edited; callable-value-flow failed once on
its TIMING budget (2.006 > 1.9) with a byte-identical fingerprint, then passed
twice at 1.788 / 1.813 — machine load, not a regression.
* fix(review): let a file's own `@import` outrank the workspace-wide class fallback
Found by running the local preflight harness before pushing rather than after.
One of its five findings is a real hole in R2-2; the rest are documentation that
overclaimed.
R4-1 (valid, REPRODUCED) — the container channel preempted the file's own
`@import`. R2-2 added the module channel as a FALLBACK after
`findClassBindingInScope`, and that order is wrong: `findClassBindingInScope`
does not stop at the scope chain. When its `isClassLike` walk misses — and a
namespace handle binds a Module, so it always misses — it falls back to
`scopes.qualifiedNames`, a workspace-wide index, and answers with the unique def
of that name anywhere in the repo. A container named `dom_utils` in a file
`Element.zig` never imports therefore captured
`bridge.accessor(dom_utils.compare, …)`, binding
`Method:src/webapi/decoy.zig:dom_utils.compare#2` while the module channel that
would have answered correctly was never reached. R3-1's shadow guard cannot
catch it: the import binds at MODULE scope, which that guard treats as the floor.
Fixed by trying the module channel FIRST. An `@import` written in this file is
the strongest available statement about what the name means here and outranks a
global uniqueness guess; when the handle is not an import of this file the
channel answers nothing and the container path runs exactly as before. Pinned by
`decoy.zig` and a strengthened assertion on the existing module-owner test,
which fails on the old order.
R4-2 — the cause documentation named shapes nothing captures. `tools.ts`
illustrated `callableValueReferences` with "a callback argument", "a stored
function pointer" and `qsort(xs, n, sz, compareItems)`. Only Zig captures a call
argument or a const initialiser; JS/TS capture only object-literal property
values, and C has no value-ref rule, so the `qsort` example is counted in no
language. Both cause blocks now name the captured shapes and say that a bare
JS/TS callback argument is not among them, so a 0 does not rule it out.
R4-3 — the same block exempted itself from the re-index caveat this PR proves it
needs. "Read from the graph, so it needs no index-time metadata" is true of the
probe and false of the edges: an index built before a language emitted these
captures has none and reports 0 — the warm-cache failure R2-1 bumped
SCHEMA_BUMP for. It now says to re-analyze before reading a 0 as measured.
R4-4 — the dispatchability canary was narrower than its own promise. Its header
claimed it fails on "a fourth language emitting value-ref at all"; it reads
`languages/<dir>/query.ts`, so Vue — which owns no query and borrows
`emitTsScopeCaptures` / `emitJsScopeCaptures` — emits value-refs while the
assertion lists three languages, and a capture synthesized in code is invisible
to it. The case now asserts on query OWNERS, which is what it checks and is
sound because a delegating language inherits the rules it borrows; the header
states the synthesized-capture gap instead of letting a green tick imply it away.
R4-5 — the BARE docstring described a lexical walk that is not one.
`findCallableBindingInScope` applies the callable predicate WHILE walking, so a
nearer parameter or local is stepped over: the defect R3-1 fixed on the container
channel, unguarded here, pre-existing since #2437 and reachable in JS/TS. Out of
scope for #3399, so behaviour is unchanged and the sentence now says what the
walk does rather than implying a guarantee it does not give.
Gates: tsc --noEmit clean, npm run build clean, prettier clean.
`test/integration/resolvers` 3,632 passed / 3 skipped (70 files);
`test/unit/scope-resolution` 2,015 passed (120 files);
`impact-callable-value-references` 7 passed under `lbug-db`. Bench --check:
receiver-resolution, zig-cross-file-resolution, scope-capture (15 languages),
scope-emission, callable-value-flow all PASS with no baseline edited.
* fix(review): honour the hub opt-in for value references, so `hub.fn` means one thing
Review round 5 (`gitnexus-check` bot on `1c7c05ff`). One finding, valid and
reproduced before fixing.
`findNamespaceValueRefTarget` accepted only `ref.origin === 'local'`. R2-2
recorded that as deliberate — "the `namespaceExportsIncludeImportedNames` hub
opt-in is a provider decision this language-neutral pass does not make" — and
that reasoning was wrong twice over. Zig sets the flag
(`languages/zig/scope-resolver.ts:41`, measured on ghostty and tigerbeetle before
it landed), and the pass does have the provider in scope: `runScopeResolution`
takes one and already forwards several of its hooks to other passes.
The result was the exact asymmetry R2-2 argued against in its own first
paragraph. A Zig hub declares nothing — every name it publishes it imported — so
requiring a local declaration declines every member reached through one:
// hub.zig
pub const scale = @import("dom_utils.zig").scale;
// Element.zig
const hub = @import("hub.zig");
pub fn callsThroughTheHub(v: u8) u8 { return hub.scale(v); } // resolved
pub const scaled = bridge.accessor(hub.scale, null, .{}); // declined
One name meaning two different things depending on whether a `(` follows it.
Fixed by forwarding `provider.namespaceExportsIncludeImportedNames` into the pass
and consulting the published channel when it is set — the same question
`receiver-bound-calls` Case 1 asks, through the same `lookupBindingsAt` read
`findExportedDefIncludingImportedNames` performs for the CALL form. The pass
still names no language; it asks the provider, which is the sanctioned hook.
Precedence is unchanged where it mattered: a locally declared member still wins
over a republished one, two distinct defs under one name still resolve nothing,
and `CALL_TARGET_TYPES` still gates the answer — pinned by `hub.DEFAULT_NS`, a
re-exported CONSTANT, which stays unregistered. Languages that do not opt in are
unaffected: the parameter defaults to `false`, and passing `false` was verified
to fail the new hub test, so it is load-bearing. The fixture republishes a member
(`scale`) that nothing else uses, so the hub assertion cannot be satisfied by an
edge another case emitted.
Gates: tsc --noEmit clean, npm run build clean, prettier clean.
`test/integration/resolvers` 3,634 passed / 3 skipped (70 files);
`test/unit/scope-resolution` 2,015 passed (120 files);
`impact-callable-value-references` 7 passed under `lbug-db`. Bench --check:
receiver-resolution, zig-cross-file-resolution, scope-capture (15 languages),
scope-emission, callable-value-flow all PASS with no baseline edited.
Also recorded in DECISIONS.md: `/autofix` returned "No successful autofix run"
because the `PR Autofix` run on `1c7c05ff` failed at `actions/upload-artifact`
with a 403 from GitHub's artifact storage, not because of anything in this
branch. This push triggers a fresh run.
* fix(review): inspect the module scope in the owner-shadow guard, and check the TSX suffix
The `gitnexus-check` pass on `bd6e577e` carried three findings of its own, and I
answered the later `1c7c05ff` pass without noticing them. Recording that as a
process failure too: bot reviews are per-head, and a later pass does not
necessarily repeat an earlier one's findings.
R6-1 (Error, valid, REPRODUCED) — the shadow guard stopped one rung short.
`isOwnerNameShadowedBySomethingElse` returned `false` on reaching the module
scope, justified as "a container declared there IS the binding, and the caller
already resolved it". That holds when the owner came from the scope chain and
fails when it came from the workspace-wide qualified-name fallback:
// Gauge.zig — never imported by Element.zig
const Gauge = @This();
pub fn read(self: *Gauge) u8 { … }
// Element.zig
const Gauge = @import("dom_utils.zig").DEFAULT_NS; // NOT a container
pub const level = bridge.accessor(Gauge.read, null, .{}); // → Gauge.zig's read
`findClassBindingInScope` steps over the module-scope binding because it is not
class-like, answers from `scopes.qualifiedNames`, and the guard waved it through.
Worth recording: the first fixture attempt did NOT reproduce. A local
`const Gauge: u8 = 3;` also claims the workspace qualified name `Gauge`, leaving
two candidates, and the fallback refuses to guess between two — so the shape
defeated itself. Binding the name by IMPORT claims no qualified name, the
fallback stays unique, and it fires. A negative result on the first shape was not
evidence the finding was wrong.
Fixed by inspecting the module scope as the last rung instead of skipping it.
The identity exemption is what makes that safe where `isNamespaceNameShadowed`
cannot do it (#2723: a namespace import writes its own name into the module scope
and would read as its own shadow) — the binding that IS the owner exempts itself,
and only a binding to something else answers `true`. `lookupBindingsAt` is
consulted at that scope and only there, because an imported alias lives in the
finalized channel rather than in `scope.bindings`.
R6-2 — the hub re-export finding, already fixed in `5d8fe9d8`; the same defect
restated on the later head.
R6-3 (valid, fixed) — the dispatchability canary omitted the TSX suffix.
`getTsScopeQuery` analyzes a `.tsx` file with `TYPESCRIPT_SCOPE_QUERY +
TSX_JSX_QUERY_SUFFIX`, and the test read only the base, so a `value-ref` rule
added to the suffix would be emitted in TSX analysis with the canary green. The
suffix is now exported and concatenated into the check; verified load-bearing by
adding an unkeyed `jsx_expression` value-ref rule to it, which fails the
TypeScript case.
Gates: tsc --noEmit clean, npm run build clean, prettier clean.
`test/integration/resolvers` 3,635 passed / 3 skipped (70 files);
`test/unit/scope-resolution` 2,015 passed (120 files);
`impact-callable-value-references` 7 passed under `lbug-db`. Bench --check:
receiver-resolution, zig-cross-file-resolution, scope-capture (15 languages),
scope-emission, callable-value-flow all PASS with no baseline edited.
* perf(bench): gate callable-value reference resolution on linear scaling
`resolveValueRefTarget` (#3399) replaced one lexical walk with four
channels, and the last of them — a qualified receiver resolved through
`findClassBindingInScope` — falls back to `scopes.qualifiedNames`, a
WORKSPACE-WIDE index consulted once per site. Keyed that is O(1); scanned
it is O(files) per site, and a registration table that costs O(sites)
today costs O(sites x files) tomorrow. Nothing in the suite can see that:
the fixtures are single-file, and the corpus it actually matters on is
lightpanda-io/browser, where `bridge.{accessor,function,…}` appears 2,047
times across 257 files.
`bench/value-ref-resolution/measure.mjs` builds two synthetic Zig corpora
of identical shape 4x apart in file count, and times ONLY the per-site
resolution loop — extraction, `reconcileOwnership` and finalize are setup.
`linear_factor` is `(t_large/t_small)/(N_large/N_small)`: measured
1.00-1.09 across runs, with `us_per_site` flat at 1.4-1.5 between the arms.
Correctness comes first, because a timing gate alone is satisfied by a fast
wrong answer: exact site/resolved/declined counts per arm plus an
order-independent sha256 over every (site -> resolved target) pair. The
corpus exercises all four channels — CONTAINER, NAMESPACE, HUB and BARE —
and carries two DECLINE controls per module (a non-callable namespace
member, a non-callable bare argument), so a widened callable gate moves
`declined` instead of hiding inside the timing.
Verified load-bearing rather than assumed: making the qualified lookup scan
a workspace-sized collection leaves the fingerprint IDENTICAL and takes
`linear_factor` to 3.752 against a slack of 1.375 — the regression class
this exists for is exactly the one no correctness gate can see.
Zig is the corpus because it is the only language whose provider sets
`namespaceExportsIncludeImportedNames`, so it is the only one that can
exercise the hub channel at all; the pass itself names no language.
`resolveValueRefTarget` is exported for the bench. Timing
`emitPropertyDispatchCalls` instead would fold the signal into edge
emission, and re-implementing the channel order in the bench would pin the
bench's idea of the function rather than the function.
* feat(zig): index every build package in the repo, not only the root one
Zig already had workspace setup — `loadZigBuildConfig` has parsed
`build.zig.zon` `.path` deps, the root `build.zig`'s named modules and each
build module's own `addImport` table since #1432, threaded through
`ScopeResolver.loadResolutionConfig` exactly as tsconfig is for TypeScript.
What it did not have is the part `tsconfigFor` supplies: per-package scope.
`loadZigBuildConfig` reads `<repoRoot>/build.zig{,.zon}` and nothing else, so
a repo laying its packages out as `packages/<name>/build.zig` — no root build
files at all — got `null`, and EVERY bare `@import("<module>")` in it went
unresolved. Cross-file resolution silently degraded to relative imports.
Measured on a two-package probe before this change: `config = null`,
`@import("core")` from `packages/app/src/main.zig` → `null`.
`loadZigWorkspaceIndex` discovers packages the way `findTsconfigFiles`
discovers configs — one bounded breadth-first walk that skips the hardcoded
ignore set — and `zigPackageFor` is the `tsconfigFor` analogue: the nearest
enclosing package governs a file, deepest-first, with no fall-through to an
outer package. Fall-through is what makes a vendored dependency's
`@import("config")` resolve to the outer repo's `config` module, the same
failure `loadTsconfigIndex` documents for a package declaring no `baseUrl`.
`resolveZigImportInternal` is UNCHANGED — impact analysis puts it at HIGH risk
with 6 dependents, and it does not need to move: it is handed one package's
config, and which config it receives is the only difference. Its 48 existing
tests pass untouched. A nested package's paths are rebased to repo-relative at
load time (`.path = "../core"` → `packages/core`, which the root-relative
reading rejects outright as an escape); the ROOT package keeps its raw
spelling, which is what `parseZigBuildZon` promises and its tests pin, so a
single-package repo is byte-identical.
`loadImportConfigs` — which runs unconditionally for every repo, Zig or not —
keeps calling the root-only loader, so no non-Zig repo pays for the walk. That
is the split TypeScript already has between the cheap `loadTsconfigPaths` and
the repo-walking `loadTsconfigIndex`, which `loadResolutionConfig` reaches only
during a language pass.
The `zig-monorepo` fixture carries the discriminating case rather than only the
happy path: `tool` binds the alias `core` to its OWN `src/core.zig`, so a
repo-wide flattened module map — the shape a workspace index invites — would
point `measure` at `packages/core`, a confident edge into a package `tool` does
not depend on. That is worse than the unresolved import this fixes, and only
per-package scoping keeps them apart.
Tests: 7 unit cases pinning the index (including `loadZigBuildConfig` answering
`null` on the same fixture, side by side) and 3 integration cases pinning the
EDGES — cross-package CALLS from the module root AND from a non-root file, and
`tool` not crossing over. Verified load-bearing: restricting the index to the
root package fails all three.
Second consumer caught by the integration test and fixed with it:
`populateZigWorkspaceStaticGating` reads the same `resolutionConfig`, per file
because two files of that pass can belong to different packages.
Gates: build clean, `tsc --noEmit` clean, prettier clean, eslint 0 errors.
`test/integration/resolvers` 3,768 passed / 4 skipped, `test/unit/scope-resolution`
2,015 passed. Bench `--check` with NO baseline edited: `receiver-resolution`
(whose corpus is `test/fixtures/lang-resolution`, where the fixture lands),
`zig-cross-file-resolution`, `value-ref-resolution`, `scope-capture` (15
languages), `import-target`, `callable-value-flow`, `scope-emission`.
* perf(zig): dequeue the package walk by head index, not `shift()`
`findZigPackageDirs` pushes children while it drains the queue, which keeps
the array in a mode where `Array.prototype.shift()` memmoves the whole
remainder rather than taking V8's left-trimming fast path — so the walk is
quadratic in the frontier, bounded only by `ZIG_SCAN_MAX_DIRS` (20,000).
Measured at that bound rather than estimated from the move count: 53 ms at
fan-out 4 and 81 ms at fan-out 20, against 0.8 ms with a head index — 66-106x,
and paid before a single config is read.
FIFO order is unchanged, and the package ordering never depended on the walk
anyway: `loadZigWorkspaceIndex` sorts by (dir length desc, localeCompare).
Memory is unchanged too — the entries were already retained by the pushes;
`shift()` only dropped the head.
`findTsconfigFiles` and `loadNodeWorkspacePackages` carry the same walk with
the same bound and are equally affected. Deliberately NOT fixed here: they are
pre-existing, outside this PR's diff, and reach TypeScript/Node import
resolution, which nothing in this change set covers. Filed separately.
Reported by gitnexus-check on
|
||
|
|
b60c21d05d
|
fix(group): extract NestJS GraphQL contracts on real indexes (#3201) (#3227)
* fix(group): extract NestJS GraphQL contracts against real 0-based indexes (#3201) Provider lookup used 1-based startLine while the graph stores tree-sitter rows, so every resolver missed. Also try PascalCased Document names and inline sibling FragmentDoc interpolations from graphql-codegen output. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(group): bind arrow-field providers and fail closed on interpolations Match Method startLine to the public_field_definition wrapper, decode template escape sequences, and reject FragmentDoc names that mix static and dynamic declarators. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(group): only inline interpolated templates under a gql tag Cooked reconstruction is not the runtime value for String.raw or unknown tags, so those interpolations stay fail-closed. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(group): tighten gql-tag trust and PascalCase Document lookup Only the identifier `gql` is a trusted interpolating tag. Underscored operation names now try the full pascal-case Document candidate graphql-codegen emits. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(group): memoize GraphQL interpolation source resolution Avoid exponential re-walks when the same fragment name is declared twice at each layer of a ${FragmentDoc} chain. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(group): fail closed on invalid tagged-template escapes Treat line continuations as empty cooked text and reject \8/\9 plus legacy octals so reconstructed gql source matches runtime. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
20b13b3ed6
|
perf(resolution): avoid quadratic config walk queues (#3237) | ||
|
|
a839d029ac
|
feat(api): report branch and index freshness on the serve repo routes (#3232)
* feat(api): report branch and index freshness on the serve repo routes `GET /api/repos` and `GET /api/repo` now return the branch an index was built from, its `lastCommit`, and — where it can be computed — how far behind the working tree it is. All of it already existed: the fields are on `RegistryEntry`, and `checkStalenessAsync` is the helper MCP `list_repos` and `gitnexus status` already use. Only HTTP never asked. #3199 made this pointed. A branch-pinned analyze registers its own entry, so one repository yields two rows in /api/repos, and telling them apart over HTTP meant pattern-matching the clone-directory suffix — a layout detail that is trimmed for long refs and absent for path-registered repos. Staleness is reported in the shape `list_repos` already returns: present only when the index is behind, carrying commitsBehind and hint. It is meaningful for path-registered repos; a url-registered repo is cloned --depth 1, so its recorded commit is HEAD and a diverged history cannot be walked by rev-list anyway. Documented in repo-projection.ts rather than left to be discovered. The projections live in their own module so the field list is assertable. Route-inline, they were reachable only by booting a server, which is how `branch` stayed unexposed while `gitnexus list` printed it. /api/repos also gains the rate limiter it lacked: this change makes one unauthenticated GET cost a `git rev-list` per registered repo, the shape this codebase already limits elsewhere. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(api): bound the staleness probe and keep liveness off the fan-out route Addresses the tri-review on #3232. P1. `checkStalenessAsync` caught every git ERROR but not a HANG — a working tree on a disconnected mount or behind a stuck lock never settles, and /api/repos fans that out once per registered repo. Worse, the web liveness probe used /api/repos on a 2s budget and re-polls on failure, so each failed probe stacked another N children on a server that was actually healthy. Two independent fixes, because either alone still leaves a sharp edge: - `execFileAsync` now carries a 5s timeout, so a hang is killed and routed into the same fail-closed "not stale" answer the existing catch already gives a bad SHA. This also protects MCP `list_repos`, which shares the helper. - `probeBackendStatus` (and `isDatabaseReady` through it) asks /api/health instead. Liveness should not cost one subprocess per indexed repo. Health sits behind the same /api/* edge gate, so the 401 "gated vs absent" distinction is preserved. P2. rate-limit.test.ts pins which routes carry a limiter so a dropped one fails a test before CodeQL has to catch it; /api/repos was newly limited but not pinned. Added, and verified it fails when the limiter is removed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
25974caf05
|
fix(doctor): distinguish vector capability from repository index state (#3228)
Co-authored-by: Eva <eva@100yen.org> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
e2da8d90ce
|
test(eval): run the benchmark offline against a scripted provider (#3235)
* feat(eval): a scriptable stand-in for Anthropic and OpenAI Every defect this harness shipped last round was invisible to its own tests for one reason: the tests exercised a layer BELOW where the code runs. The usage log was never written because the proxy is a subprocess with a constructed environment. The callback could not be imported because LiteLLM loads it by path, not as a package. Failures went unrecorded because only the async hook was overridden. CI or review caught all three; no unit test could, because each called the function directly instead of driving the path that calls it. This closes that gap without spending money. It speaks the two wire protocols the harness actually depends on - Anthropic Messages, streaming and not, and OpenAI Responses - so a run can go through the real sandbox, the real CLI, the real gateway and the real usage callback with only the model faked. The runner already supports pointing at it: --base-url is the same path the free-model proxy documentation uses. Scripted rather than simulated. A test decides what the model says, which tools it asks for, and exactly what usage it reports. That last part is what makes provider-native accounting testable at all: real cache hits are not reproducible on demand, but a declared cache_read of 44,000 is. One Reply served down both protocols is also the cleanest demonstration that the same billed work is stated as a sum on one side and as a whole on the other. Tool blocks are the mechanism for artifact-producing cells. The CLI runs what it is asked to run, so a scripted Write block makes it write that file inside the sandbox for real - no model deciding anything. The end-to-end test drives the real proxy against the mock and asserts the usage log records the provider's own arithmetic through the Anthropic-shaped translation. It SKIPS here, because litellm's console script is absent in this environment, so it is unverified until CI runs it - the same footing the bubblewrap canary started on, and that one found a real bug on its first CI run. Not yet built: driving a whole sweep against this. That needs a scripted reply sequence that carries a cell to a scored artifact, which is the next step and the point of the exercise. 668 eval tests pass, 17 skipped; the two test_model_gateway.py failures are the pre-existing environmental ones. * test(eval): run a real session against the scripted provider The mock only proves something once the harness runs against it. This adds the stand-in CLI and the first integration tests that use it, so a session goes through the real code with only the model faked. tests/fixtures/fake_claude.py does what the CLI does at the two boundaries the harness depends on: it calls ANTHROPIC_BASE_URL for a turn, EXECUTES the tool blocks that come back, and prints the stream-json sequence the parent parses. Everything between - the session runner, the event-stream parse, the usage extraction, the artifact capture, the scorer - stays real. Four tests, chosen for the layers that have actually broken here: the usage a provider reported survives to the row, a scripted Write produces an artifact parse_review_output accepts, the prompt the harness meant to send is what arrived, and an upstream 529 lands as a failed session rather than a usable measurement. Writing the stand-in found two things worth keeping. The prompt arrives on STDIN under "-p --input-format text"; scanning argv for a non-flag token picks up a flag's value instead, and the prompt-fidelity test is what caught it. And three of these tests had been holding a sandbox they never applied, since no command_prefix is passed - that implied coverage which was not there, so the sandbox is gone from them and stays only in the artifact test, which needs its review directory. What these do NOT cover, checked rather than assumed: making the stand-in write in place instead of atomically still passes. On the host-unsafe backend there is no read-only mount to refuse it, so the atomic-write requirement remains a bubblewrap mount property that only the real-sandbox canary can prove. Dropping cache_read from the recorded usage does fail, so that half is genuinely pinned. 672 eval tests pass, 17 skipped; the two test_model_gateway.py failures are the environmental ones. * fix(eval): the usage adapter read a shape the callback never receives Running the gateway against the scripted provider proved the accounting merged in #3220 does not work, and the same run showed why nothing had caught it. LiteLLM does not hand a logger the upstream body. It normalises usage into its own Chat-Completions-shaped object first, so an OpenAI Responses reply reaches the callback as prompt_tokens / prompt_tokens_details.cached_tokens - never the input_tokens / input_tokens_details the shipped adapter reads. Every field came back unknown. The observed call_type is "anthropic_messages" as well, because Claude Code calls the Anthropic-shaped endpoint, so canonical_provider returned None and normalize_usage would have refused outright. Both were assumptions about a boundary I had only read about. The unit tests agreed with them because their fixture was written in the same wrong shape, so producer and consumer were consistent and both wrong - the exact failure the producer/consumer round trip exists to catch, one layer further out. Adds a LITELLM_NORMALIZED adapter for the object that actually arrives. The arithmetic is still OpenAI's - prompt_tokens is the whole, the details are subsets - so ordinary input is recovered by subtraction. The Responses adapter stays for a raw upstream body, which the mock still serves and tests directly. An unrecognised provider is still refused rather than guessed. The fixtures now carry the measured shape, and the end-to-end test asserts it through a real proxy: 48k prompt tokens with 44k cached is read back as 3k ordinary rather than as silence. 676 eval tests pass, 16 skipped, none failing. * test(eval): run a whole sweep offline, with negative controls The layers between a model turn and a promotion decision had never been exercised together. Unit tests covered each alone, and the paid runs that would have covered the composition kept dying, so the contracts BETWEEN them went unverified - which is where this harness has repeatedly shipped bugs. Drives runner.main() the way the workflow does. Real task selection, hidden oracle capture, sandbox, CLI subprocess, artifact capture, scoring against the oracle, aggregation, health guard and promotion gate. Only the model is scripted. Getting to green meant satisfying nine real contracts nothing had exercised end to end, and each failure was the harness correctly refusing bad evidence: --unsafe-no-bwrap is restricted to the paired review arms; ce_* needs a plugin carrying ce-plan, ce-work and ce-code-review; candidate_* needs an overlay; the clone needs .gitnexus/meta.json with indexedAt and lastCommit; the evidence gate needs a Skill request with a non-error result; review findings need exactly ten fields with severity in critical/high/medium/low; and the hidden labels use a DIFFERENT schema from the review output - line_start/line_end, six fields. That last one only a real run surfaces. Three negative controls, because a scorer that cannot be wrong measures nothing. A finding in the wrong place is tp=0 fp=1 fn=1 and oracle-failed, while its evidence stays VALID - being wrong is a quality result, not a broken measurement. Approving defective code is a miss with no false positive, and precision is None rather than 0, because it is undefined with no predictions. One run cannot promote: the gate says it needs three valid paired runs. A fourth control exists because a mutation demanded it. Forcing skill_was_invoked_events to return True left every other test here passing, so nothing pinned the gate that separates measuring a SKILL from measuring a model. Writing it turned up behaviour worth recording rather than assuming: a skill-not-invoked row still carries its score AND still counts toward the arm median, because aggregate() drops EXCLUDED_ERROR_KINDS and evidence_valid=False and skill-not-invoked is neither. The health guard stops the sweep, so a single-run sweep cannot promote on it, but a mixed run's median would include a cell whose skill never ran. Pinned as-is so it cannot change silently in either direction; changing it is a promotion-semantics decision, not a test fix. Two provisioning steps are stubbed and neither is harness logic: the pinned runtime mounts (no node_modules in a worktree) and the sanitized graph build (needs the gitnexus CLI at a mounted path). Containment is host-unsafe here; bubblewrap stays with the real-sandbox canary. 681 eval tests pass, 16 skipped, none failing. Runs in ~18s. * fix(eval): an uninvoked skill must not move the arm's quality median Found by the offline sweep: a skill-not-invoked row still carried its score into the arm's quality median. aggregate()'s filter dropped EXCLUDED_ERROR_KINDS and evidence_valid=False, and skill-not-invoked is neither, so an arm could be credited for a review it never performed with the skill under test - which is the one thing an arm exists to measure. Excluded from the QUALITY metrics only. Cost and duration still count that row, because the session really ran and really was billed, and the promotion gate still sees it, because it has its own vocabulary for a candidate that never loaded its skill. Two wider fixes were tried and abandoned, both because the tests said so rather than because I reasoned it out first. Reusing the health guard's evidence_failed predicate also excluded transcript-missing rows, but test_aggregate_excludes_session_error_rows_from_medians pins those as counting: that session ran, only its transcript is unverifiable. Excluding the row from `valid` outright turned a candidate whose skill never loaded from keep_incumbent into insufficient_evidence - the safety property held either way, but the decision vocabulary is promotion semantics and not mine to change on a measurement fix. Mutation-checked: putting the rows back into the quality median fails the new test. Both directions asserted, since a filter that excludes everything would also pass - a wrong-but-valid review still moves quality, because being wrong is exactly what a quality median should reflect. 682 eval tests pass, 16 skipped. * test(eval): run the offline sweep unstubbed in the job that can, and probe CLI identity Items 5 and 6 turned out to be one change. The containment (ubuntu) job already installs bubblewrap, the pinned Claude CLI, node_modules and a built GitNexus - everything the sweep's two provisioning stubs stand in for. So the stubs are not a property of the test, only of a machine that lacks those things. GITNEXUS_REQUIRE_FULL_SWEEP=1 makes the sweep run with nothing stubbed: real containment instead of --unsafe-no-bwrap, the real runtime mounts, the real sanitized graph. Set in that job, following the GITNEXUS_REQUIRE_BWRAP_CANARY pattern already there. The gate FAILS on a missing piece rather than degrading to the stubbed path, which is the point - a green tick that silently tested less is what the bubblewrap canary was written to prevent. Verified both states here: default green, and gate-on fails on this machine rather than skipping, since it cannot create user namespaces. Item 7 is an experiment, not an answer. Per-cell attribution needs an identifier that travels WITH the request, because one proxy serves the whole sweep and anything read from its environment is identical for every call. What the real CLI sends is not documented anywhere I can check, and guessing a wire format is exactly how the last three accounting bugs happened. So the probe drives the REAL pinned CLI against the mock and records the identity-bearing headers and body keys that arrive. It asserts only that a request was made; the recorded evidence is the deliverable, and the job log preserves it. Skips without CLAUDE_CANARY_BIN. Two guards caught this rather than review: the repo pins the containment job's env and its exact test list, so both had to be updated deliberately - which is the guard working, not friction. 682 eval tests pass, 17 skipped. * test(eval): make the offline sweep cross-task, so a scheduler change is checkable The sweep fixture had one task, and a single task cannot show the thing a cross-task scheduler changes: waves are per-task, so ordering, packing and a breaker spanning a task boundary are all invisible with one. A second task with its defect in a DIFFERENT file, and its own hidden labels, makes per-task routing observable. The scripted reply is now task-aware, which matters for the same reason: replying with the first task's finding scores the second task wrong. The load-bearing assertion is that each task scored against ITS OWN oracle. That is the dangerous failure mode of interleaving cells from different tasks - a mis-routed context or artifact scores one task against another's labels, and every row still looks green. Mutation-checked: pointing every cell at the first task's oracle snapshot fails it. This is the safety net the packed-scheduler wiring needs. Measured earlier against the real sweep_packed_cells, that change is worth -27% on a cold sweep and -37% weekly, with breaker fidelity holding at three injected failure positions - but it restructures a 125-line loop across ~92 names that also holds graph prefetch, reuse selection, oracle staging and the canary drop. Landing that on top of a one-task fixture would have been unverifiable, which is why this comes first and separately. 682 eval tests pass, 17 skipped. * fix(eval): commit the stand-in CLI's executable bit The file was created and chmod +x'd locally, but committed 100644 - so the mode existed only in my working tree. Any fresh checkout, CI included, gets a non-executable file and every cell dies with "required executable is not an executable regular file". Found by accident: checking out origin/main and back to compare a flaky test restored the file from the index and stripped the bit, which turned 5 green tests into 9 failures. Without that detour this would have failed on the first CI run instead. Same shape as the bugs this branch exists to catch - something that works only because of local state, breaking where the code actually runs. * fix(eval): apply code review findings Seven local reviewers and an independent cross-model pass. The headline is that a fix I added in this branch was worse than the gap it closed. Reverted the aggregate() quality-median filter. Excluding skill-not-invoked rows from the quality metrics left valid_runs and excluded_runs still counting them, so the promotion gate saw N clean runs while the median came from fewer. The dropped rows are systematically an arm's worst, so it biased toward PROMOTING - reproduced: one real run at 0.9 plus two uninvoked rows at 0.0 gave the gate 3 valid runs, zero exclusions and a 0.9 median, flipping keep_incumbent to promote. Three verdict fields compounded it: they are all() reducers still reading the wider set, so one uninvoked cell flipped a whole arm. Five reviewers found the two halves independently. Closing it honestly needs a scored-run count plus a paired-equality check in the gate, which is promotion semantics rather than an aggregation fix. The gap is now pinned by a test that states why the half-fix was reverted. Stopped forging the absence of CI. The runner refuses --unsafe-no-bwrap when CI is set because that mode runs sessions with bypassPermissions behind a boundary its own docstring calls "not a security boundary"; the sweep test deleted CI to get past it, so eval / locked pytest ran an uncontained agent sweep on the runner holding the checkout and credentials. It skips under CI instead - the containment job still runs it for real with GITNEXUS_REQUIRE_FULL_SWEEP=1. The stand-in CLI was lying in three ways. It never set is_error, so a refused write read as a completed one. It had no Skill branch at all, so honoring is_error revealed the evidence gate had been satisfied by a tool the fixture never ran - the gate was measuring the fixture, not a skill. And a reply with no usage became four zero-valued fields plus a fabricated cost, which is exactly the unknown-is-not-zero confusion the accounting it feeds exists to prevent. A provider failure also crashed the subprocess with no terminal result event. The identity probe never ran anywhere. test_mock_provider.py was in no job's file list, and the only job setting CLAUDE_CANARY_BIN runs a fixed list. My commit message claimed the next containment run would produce the answer; it would not have. Now wired in, with the CI-shape test updated to pin it. Also: the regex-miss fallback wrote a predictable name in shared /tmp through a symlink-following stage, now scoped to the test's own directory; and the canonical_provider docstring plus the callback comment still asserted a call_type branch the code no longer has. Deferred as design decisions rather than review fixes: the containment sweep uses the stand-in CLI rather than the pinned real one, the full-sweep path bypasses the gateway so native usage accounting is unexercised there, _normalize_litellm duplicates the Responses algorithm, and OPENAI_RESPONSES is now unreachable from canonical_provider. 682 eval tests pass, 17 skipped, ruff clean. * fix(eval): carry scripted tools over the Responses protocol Review round on #3235. Three real items; five more were already fixed in |
||
|
|
4154b63131
|
feat(indexing): add Objective-C semantic indexing support (#3179)
* docs: add Objective-C fork provider notes * feat(objective-c): add deterministic provider and grammar * feat(objective-c): finalize provider MVP * fix(objective-c): harden provider integration * fix(objective-c): normalize bare macro markers * docs(objective-c): integrate provider documentation * fix(objective-c): harden resolution and header classification * fix(objective-c): complete provider follow-ups * fix: address Objective-C review follow-ups * chore: format Objective-C grammar sources * fix(objective-c): harden review follow-ups * Address PR review feedback (#3179) Keep Objective-C chunking and macro recovery aligned with the grammar, and stop Community MEMBER_OF edges from leaking into symbol context. Co-authored-by: Cursor <cursoragent@cursor.com> * Address follow-up review on ObjC chunking and language fallback. Keep preprocessor directive text from changing file-scope brace depth, group real ivar nodes, skip header modifiers, and restore Rakefile/Gemfile detection through getLanguageFromFilename. Co-authored-by: Cursor <cursoragent@cursor.com> * Parse Objective-C headers with the objc grammar in embeddings. ensureAndParse and structural extraction now use the same content classifier as ingest, including method snippets from .h files, so Protocol/Category/Class chunks are not re-parsed as C++. Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3179) Keep file-scope macro elision off C line splices and @interface/@protocol/@implementation bodies, and attach ivar attributes to the following instance variable when chunking. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(bench): rebaseline Objective-C CSV emit * feat(objective-c): add workspace resolution and linear emit benches Plain .h files are classified as C++, so the ObjC pass could not resolve #import of those headers. Load a C/C#-style workspace once per pass, and keep protocol-candidate USES linear. Refs #3179 Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3179) - Compare LadybugDB labels() as a scalar when excluding Community MEMBER_OF edges. - Walk superclass members, skip file-static C sibling defs, and ignore comments in ObjC header/macro scans. Note: pre-existing failure in objective-c-provider integration (worker-pool ready timeout) not addressed by this PR. Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3179) Emit Objective-C declaration captures so compilation-unit siblings can share header/implementation bindings, and keep class vs protocol visibility groups distinct. Note: pre-existing failure in worker-pool startup (GITNEXUS_WORKER_READY_TIMEOUT_MS) not addressed by this PR. Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3179) Emit every comma-separated property/ivar declarator, and count @interface after a multiline block comment closes so in-declaration macros stay intact. Note: pre-existing failure in worker-pool startup (GITNEXUS_WORKER_READY_TIMEOUT_MS) not addressed by this PR. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: ximengkai <ximengkai@soyoung.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
1e8bdd890a
|
fix(ci): look up fork prebuild PRs by head owner and branch (#3236)
* fix(ci): look up fork prebuild PRs by head owner and branch
commits/{sha}/pulls is empty for fork SHAs, so deliver-fork-prebuilds failed closed on every real fork PR. Resolve the open PR from workflow_run head owner+branch instead.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ci): use the same fork-PR lookup in autofix publish
pr-autofix-publish had the same commits/{sha}/pulls fallback, which is empty for fork SHAs. Share the pulls?head=owner:branch verifier and always run it before sticky comments or check runs.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ci): fail closed on ambiguous fork-head PRs
Untrusted artifact pr_number must not pick among sibling open PRs from the same fork branch. Parse paginated gh --slurp pages and require verify success before prebuild checkout/push.
Co-authored-by: Cursor <cursoragent@cursor.com>
* style(ci): prettier the fork-PR identity verifier
Root prettier --check fails on .cjs; lint-staged only formats .js.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
|