* fix(query): send hub content once across process_symbols rows
With include_content, a symbol in several execution flows carried its
full source text on every (id, process_id) row. Keep content on the
first row for each symbol id and omit it from later rows. Membership
fields, is_entry_point, and symbol_count are unchanged.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(query): point agents to the content row and lock the dedup in tests
The query tool text now says content is kept once per symbol id across
the whole process_symbols array, possibly under a different process_id,
and names context({uid, include_content: true}) as the fallback.
Tests: the integration suite asserts func:validate has two rows with
content on exactly one. Unit tests cover a later entry-point row that
is flagged and stripped, a hub in three processes, and that the shaper
does not mutate its input.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(query): state the content-once rule on include_content and in the query hint
The query tool's include_content property now says content is sent once
per symbol id, on its first process_symbols row, and that
context({uid: "<id>", include_content: true}) returns it for any row.
When a query asked for content, the Next hint adds that same fallback;
without include_content the hint is unchanged. The example call now
uses the "<id>" placeholder style used elsewhere in the tool text.
Tests: pin the property sentence, check the hint with and without
include_content, and cover a max_symbols slice that moves the content
row to a later process and a first row that is also the entry point.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* 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>
* 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>
* fix(mcp): prevent orphan processes by handling stdin close/end and startup race condition
Three gaps in stdin EOF handling:
1. Startup race: parent can die before `process.stdin.on("end", ...)` is
registered, so the event is missed entirely.
2. Missing "close" event: when pipe is forcibly closed (parent SIGKILL),
"close" fires without "end" on some platforms.
3. Transport layer did not propagate stdin termination to its onclose
callback.
Fixes:
- Check readableEnded/destroyed in start() before registering listeners.
- Register stdin end+close listeners in CompatibleStdioServerTransport.
- Add _closed guard for idempotent close().
- Throw if start() is called after close().
- Add process.stdin.on("close") in server.ts alongside existing handlers.
- Add 5 regression tests.
* fix(mcp): register stdin shutdown before server connect
* feat(review): add PR reviewer swarm agents
Seven read-only subagents coordinated by an orchestration skill for
structured, evidence-grounded production-readiness PR reviews.
Agents: facts-historian, branch-hygiene, risk-architect, test-ci-verifier,
security-boundary, docs-dod, synthesis-critic. All use Read/Grep/Glob/Bash
only — no edit tools.
Skill invoked as /gitnexus-pr-swarm-review <PR>.
* fix: patch vector extension and uncaughtException for review findings
- Add { policy: 'auto' } to both loadVectorExtension() calls in
embedding-pipeline.ts so analyze --embeddings auto-installs VECTOR
- Add void to uncaughtException shutdown(1) call for Node v20+ safety
- Re-add getExtensionInstallPolicy export + default change + 4 tests
* fix(mcp,lbug): graceful shutdown exit codes + complete offline-first VECTOR policy
Completes the two live issues PR #1161 only partially addressed.
#1132 — MCP shutdown crash: SIGINT/SIGTERM were registered with `shutdown`
directly, so Node passed the signal NAME string into process.exit(), crashing
with ERR_INVALID_ARG_TYPE ('SIGTERM'). Map signals to numeric exit codes
(SIGINT->130, SIGTERM->143) via a testable installSignalShutdown(); add an
unref'd force-exit watchdog so a hung disconnect()/close() cannot wedge
shutdown; and void the stdin/stdout handlers so event payloads never reach
process.exit() as a non-number.
#1153 — offline-first extension loading:
- semanticSearch (a query/read path) no longer forces policy:'auto'; queries
use load-only and never spawn a network INSTALL (extension.ladybugdb.com).
- the analyze embedding WRITE path resolves the policy from
GITNEXUS_LBUG_EXTENSION_INSTALL (honoring never/load-only/auto; default auto)
instead of hard-forcing 'auto', so an offline/locked-down operator's override
is respected (the regression that re-broke #1153 for the VECTOR path).
- surface the active install policy in `gitnexus doctor` (was claimed but never
delivered; also gives the previously-dead getExtensionInstallPolicy a caller).
- emit an actionable message when VECTOR is unavailable.
Tests: regression for the signal->numeric mapping (reproduces the signal-string
crash condition) and for embedding install-policy resolution. tsc/prettier clean,
eslint 0 errors, 55 unit tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(analyze): degrade gracefully when FTS extension is unavailable
The load-only default made `gitnexus analyze` throw when the FTS
extension was not pre-installed, breaking CI and offline use. Make the
analyze write path opt into the `auto` install policy (LOAD-first then
bounded INSTALL — symmetric with the VECTOR/embeddings path and the #726
contract) and degrade gracefully when the extension still cannot load:
skip search-index creation, log a warning, and complete with a fully
queryable graph (only full-text/BM25 search is disabled). `--repair-fts`
still fails loudly.
- Surface the degraded state instead of reporting healthy:
AnalyzeResult.ftsSkipped, a persistent CLI summary warning, and
meta.json capabilities.fts.status = "unavailable".
- Skip the FTS-primitive integration tests when the extension is
unavailable (shared skipUnlessFtsAvailable helper).
- Add a unit test for the degradation branch; fix the existing
full-analyze test mock that omitted loadFTSExtension.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(lbug): skip FTS-seeding suites when extension is unavailable
The withTestLbugDB helper seeds FTS indexes in beforeAll via createFTSIndex,
which throws when the optional FTS extension cannot load — failing the whole
suite on machines where it is neither pre-installed nor installable (the
macOS platform-sensitive CI runner). Probe the extension once (mirroring the
analyze write path's `auto` policy), bypass FTS seeding when it is
unavailable, and skip the suite's tests via beforeEach with a one-time
warning so the skip is visible rather than a setup crash.
Fixes the macOS failures in search-core, search-pool, local-backend-calltool,
and staleness-and-stability. Suites still run normally where FTS is available.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(mcp): add tool safety annotations
* test(mcp): address PR #1127 review follow-ups
- Replace private `_requestHandlers` SDK access in server.test.ts with
`Client` + `InMemoryTransport.createLinkedPair()` for the tools/list
annotation propagation test. The new path uses supported public APIs
and surfaces SDK changes loudly instead of silently degrading.
- Extract `OPEN_WORLD_READ_ONLY_TOOLS` set in tools.test.ts so future
read-only open-world tools can be added without rewriting the
invariant; preserves the current "only `query` is open-world" guard.
- Add inline rationale on `group_sync` annotations explaining the
conservative `idempotentHint: false` (writes contracts.json on every
call even when output is deterministic).
No runtime behavior change. Annotations themselves and tools/list shape
are unchanged.
---------
Co-authored-by: Gergo Magyar <gergomagyar@icloud.com>
* feat: configure prettier with pre-commit hook integration
Add prettier, lint-staged, and prettier-plugin-tailwindcss at the repo
root with husky pre-commit hook integration. Moves husky from
gitnexus/ to root package.json for reliable hook installation.
- Root package.json with prepare/format/format:check scripts
- .prettierrc with endOfLine:lf and tailwindStylesheet for TW v4
- .prettierignore excluding fixtures, vendor, generated, *.d.ts, *.md
- .gitattributes enforcing LF line endings for Windows consistency
- Pre-commit hook uses direct node_modules/.bin/ paths (no npx)
* style: apply prettier formatting to entire codebase
One-time bulk format. No logic changes.
Use .git-blame-ignore-revs to skip this commit in git blame.
* chore: add .git-blame-ignore-revs for prettier format commit
* perf: pre-commit hook runs only tests related to staged files
Use vitest --related to scope test execution to tests that import
the changed files, instead of running the full suite on every commit.
* perf: remove vitest from pre-commit hook, keep in CI only
Pre-commit now runs lint-staged + tsc only. Tests run in CI
(ci-tests.yml) where they belong — keeps commits fast.
* ci: add prettier format check to quality workflow
PRs will now fail if code isn't formatted with prettier.
- 59 test files covering unit and integration tests
- vitest config with coverage thresholds and fork pooling
- Test fixtures (mini-repo + multi-language sample code)
- Add vitest + coverage-v8 to devDependencies
- Add test scripts (test, test:integration, test:all, test:watch, test:coverage)
- Move typescript to devDependencies where it belongs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>