GitNexus/docs
samuel mbabhazi 02101fdfed
feat(mcp): add an opt-in, index-only refresh_index tool (#3769)
* feat(mcp): add an opt-in refresh_index tool

A local `gitnexus mcp` server started with GITNEXUS_MCP_ENABLE_REFRESH=1
lists refresh_index, which rebuilds one registered index from its working
tree through the forked analyze worker auto-sync uses, with the same
index-only options: no AGENTS.md, CLAUDE.md, skill, hook or repository
file is written.

The tool lives outside GITNEXUS_TOOLS, so the default catalog and its byte
budget are unchanged. `gitnexus serve` never passes the option, so the
web/Docker /api/mcp surface cannot list it; read-only mode hides and
rejects it through the existing allowlist.

A repository whose last index recorded more than
GITNEXUS_MCP_REFRESH_MAX_FILES files (default 5000), or none, is refused.
After GITNEXUS_MCP_REFRESH_TIMEOUT_MS the call stops waiting; the worker
never interrupts a started analysis, so it finishes in the background and
keeps the index lock. One refresh runs per repository at a time, and the
slot is held until the worker is done. A collapsed graph write is reported
as an error, as analyze and serve report it. dry_run reports staleness,
the file count and whether .gitnexusrc asks for embeddings. Worker
progress is relayed as MCP progress notifications when the client sends a
progress token. Malformed limits fail startup.

Refs #3524.

* fix(mcp): keep worker log lines out of refresh progress

The analyze worker sends its log lines as progress messages with percent
-1. The launcher now drops them before onProgress, so the listener only
sees real progress (the server relay already discarded them).

The refresh tests now unset GITNEXUS_MCP_REFRESH_MAX_FILES and
GITNEXUS_MCP_REFRESH_TIMEOUT_MS, so an inherited value cannot change the
defaults they assert. The tool-arguments comment names the schema lookup
it actually uses.

* test(mcp): check ignored paths after a refresh, not only git status

analyze excludes its own storage through .git/info/exclude, so a clean
git status says nothing about ignored paths. The e2e now also lists them
and allows none outside .gitnexus/.

* fix(mcp): count the working tree against the refresh file limit

refresh_index compared GITNEXUS_MCP_REFRESH_MAX_FILES with the file
count of the last index. A stale index is exactly the case where that
count can be far below the working tree, so a repository that grew from
100 to 50,000 files since its last index was admitted under the default
limit of 5,000 and dry_run reported 100.

countScanCandidates (filesystem-walker.ts) walks the same glob patterns
and ignore rules as walkRepositoryPaths, without stat-ing, and stops one
past the limit, so admission costs the same on a huge tree as on a small
one. Large files, non-regular entries and declaration siblings are not
subtracted, so the count is an upper bound on what analyze indexes.

refreshIndex admits the rebuild from that count. dry_run reports it as
estimatedFiles (null past the limit, where only "more than maxFiles" is
known, and with no limit, where nothing is counted) and the registry
count as lastIndexedFiles. An index without a recorded count is no
longer refused, since the tree is counted. A tree that cannot be read
is refused before the worker, like an unreadable .gitnexusrc.

Tests: the walker count matches the scan's paths, stops at limit + 1,
and counts a file the scan drops for its size. The backend refuses a
tree over the limit whose last index was under it, without starting the
worker, and does not count with no limit. The e2e test commits two files
past a limit of 4 after an index of 3 and checks that dry_run and the
refresh refuse, with the real enumeration, and that the index stays at
its commit. Reading the registry count again fails the backend and e2e
tests; dropping the early stop fails the walker test.

* test(ingestion): ignore a directory only .gitignore excludes in the count test

The fixture ignored generated/ through .gitignore, but the ignore service
already skips a root generated/ directory on its own, so the test passed
with .gitignore disabled. scratch/ is excluded by .gitignore alone.

* test(mcp): cover refresh progress and readers

* fix(mcp): retain timed-out refresh slots (#3769)

Keep the repository busy until an abandoned worker exits, so retries cannot start another worker while the original refresh is still running. Cover late success and failure, and update the timeout documentation.

---------

Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
2026-10-31 13:57:02 +00:00
..
languages docs: simplify guides and verify receiver gaps (#3694) 2026-10-23 09:06:03 +01:00
optimize fix(taint): avoid false positives for guarded Git arguments (#3703) 2026-10-24 15:37:26 +01:00
plans feat: add the test scenario map and a Vitest 5 mutation runner (#3686) 2026-10-27 16:46:58 +00:00
cli-reference.md feat(mcp): add an opt-in, index-only refresh_index tool (#3769) 2026-10-31 13:57:02 +00:00
deployment.md feat(web): resolve a root-relative GITNEXUS_BACKEND_URL against the page origin (#3640) 2026-10-24 08:45:24 +01:00
editor-setup.md feat(opencode): ship hooks as auto-loaded global plugin for full support (#3619) 2026-10-28 13:45:08 +00:00
mcp-reference.md feat(mcp): add an opt-in, index-only refresh_index tool (#3769) 2026-10-31 13:57:02 +00:00
wiki-generation.md fix(wiki): configure limits and retry truncated pages (#3775) 2026-10-31 13:24:37 +00:00