* feat(storage): resolve the shared sibling-store identity and layout (#3352) Linked worktrees of one repository resolve to one store under GITNEXUS_HOME/stores/<key>, keyed by the canonical git common dir. The resolver reads the .git entry directly, so hot paths spawn no git. Slot naming moves to a leaf module so storage-resolver and shared-store do not import each other. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(storage): resolve a shared checkout's graph and existing store slot (#3352) getStoragePaths reads a flat slot's recorded graphPath only for checkout slots under the stores directory; other paths keep <storagePath>/lbug with no I/O. A recorded path outside the store's commit graphs is ignored. resolveStoragePath falls back to an existing store slot for an unregistered checkout, so reads never move to an empty slot. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(analyze): share one immutable commit graph across clean worktrees (#3352) A linked worktree writes its own slot in the shared store. A new slot is seeded with a pointer to the nearest commit graph, so a clean checkout at an indexed commit takes the up-to-date path and writes no graph. A checkout with local changes gets a copy-on-write private graph before its first write. After a successful run, a clean checkout at HEAD publishes its graph into commits/ under a store lock, or drops it when that commit graph already exists. Commit graphs are never written after publish. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(analyze): seed a worktree's graph from the nearest index (#3352) A new shared slot is seeded from the store's commit graph nearest to HEAD, else from this checkout's or the main checkout's repository-local index (copied under that index's lock, source left in place). The run that follows is up to date or incremental instead of a full build. If a pointed-at shared graph has been removed, analyze falls back to a full build instead of an incremental update over a missing baseline. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(analyze): keep one parse cache per shared store (#3352) Linked worktrees read and write the parse cache and durable ParsedFile store under the store's caches/ directory. Before pruning, a run folds in the chunk keys recorded by every member slot and commit graph, and the fold, prune and save run under a store-wide cache lock so one member never evicts another's live chunks. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(clean): remove only what no shared-store member references (#3352) Deleting a shared checkout slot (clean, clean --all, remove, and the server delete route) recounts references under the store's publish lock and deletes commit graphs no member points at, then the store itself once empty. A graph that cannot be deleted (open on Windows) is reported and kept for the next pass. clean --gc also drops member slots whose worktree is gone or no longer resolves to the store. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(analyze): let a clone opt in to a shared store with --share-with (#3352) An independent clone joins a linked worktree's shared store only with analyze --share-with <repo>, and only when its normalized origin URL matches that member's (credentials stripped, as #2054 compares). The registry remembers the choice. --no-share moves an opted-in clone back to its own .gitnexus and reclaims its old slot; linked worktrees always share and are pointed at GITNEXUS_SHARED_STORE=off instead. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(storage): count a shared checkout's commit graph as its code index (#3352) A clean shared checkout reads a commit graph and owns no graph file, so the code-index presence check made status report it unindexed and registry validation skip it. The check now follows the slot's validated graphPath. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(storage): adopt existing worktree indexes and report shared-store state (#3352) A shared checkout gets <repo>/.gitnexus/store.json pointing at its store slot; resolution follows it only when it names that checkout's own slot. An existing local index seeds the slot and is left in place. status (text and --json) and doctor report the store, whether the graph is shared or private, and any leftover local index, which clean --local-index removes while keeping the pointer. With GITNEXUS_SHARED_STORE=off a previously shared checkout indexes into its own .gitnexus again and never writes a commit graph. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(mcp): read the graph a shared checkout points at (#3352) MCP, the HTTP API, group sync, augmentation, and the Claude hook (all three byte-identical copies) resolve a flat slot's graph through resolveGraphPath instead of joining 'lbug' onto the storage path, so checkouts at one commit share one open database. The embeddings writers (embeddings sync and the server embed job) take a private copy first and never write an immutable commit graph. The post-analyze settle probe accepts fresh metadata that points at an existing commit graph. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs: describe the shared worktree index store (#3352) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * refactor(storage): reuse helpers across the shared-store code (#3352) One graph-clone helper replaces two copy-then-rename blocks; clean and status reuse formatSlotSize; leaving a store reuses removeSharedStorePointer; withStoreLock is imported from its own module instead of a re-export. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(storage): address review findings in the shared store (#3352) - Never publish a graph whose build saw dirty files, and never trust a local-index seed on the up-to-date path; it may hold reverted edits. - Record slot pointers and reclaim under the publish lock, and reclaim right after each publish, so a commit graph is never deleted between publish and pointer save and superseded graphs don't pile up. - clean --gc decides membership from the registry, so opted-in clones and a main checkout without worktrees are not dropped. - Leaving a store re-registers first, so an up-to-date run cannot leave the registry pointing at a deleted slot. - Drop pinned branch summaries when an entry moves into a store slot. - Keep run.cjs and the AGENTS.md runner path inside the checkout. - MCP handles follow the slot's current graph, and branch scoping reads the slot's own metadata. - Cache resolveGraphPath by metadata file identity; look up opted-in entries with canonical registry paths. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(cli): localize help for the shared-store flags (#3352) --help renders option text from the i18n catalog, so --share-with, --no-share, clean --gc and clean --local-index need keys in both locales, not just index.ts literals. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(storage): lock the embed-job graph copy and skip it on forced rebuilds (#3352) The server embed job copies a shared checkout's graph under the slot's index lock, so a CLI analyze in another process cannot interleave. A forced rebuild with no embeddings to carry over drops the pointer without copying a graph it would discard unread. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(agents): bump AGENTS.md version for the shared store notes (#3352) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(storage): keep shared-store file reads inside checked paths (#3374) Address CodeQL js/path-injection and js/file-system-race on shared-store.ts: every filesystem read rebuilds its path under a fixed parent with an inline path.relative barrier, the .git probe is one read (EISDIR marks a directory) instead of stat-then-read, and the graph pointer cache stats and reads through one file descriptor. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(storage): address review feedback on the shared store (#3374) - Hold the slot index lock for the whole server embedding job, not just the graph copy. - --no-share re-registers and deletes the old slot under that slot's index lock, so a running shared analyze cannot re-register it. - clean --gc previews without --force, like every other destructive arm. - status reports a pinned branch index as private. - Slot names prefix Windows device names that carry an extension. - A slot or store named ..<name> is a legal direct child. - Shared-store suites run in the serialized lbug-db vitest project and clear an inherited GITNEXUS_SHARED_STORE. - Doc and test accuracy fixes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(storage): pin shared-store behavior for worktrees of a bare repository (#3374) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(storage): address second review round on the shared store (#3374) - Reject --no-share in a linked worktree before taking any lock or indexing anything. - clean --all and remove also delete each shared checkout's pointer. - Delete an empty store only while also holding its cache lock, and re-check emptiness under it. - A store pointer is trusted only when the slot's own metadata names this checkout; the editable pointer file just says where to look. - Device names with an extension are prefixed on Windows only, so POSIX slot names stay stable. - Test fixtures use the gitnexus-test- prefix the stale-sidecar sweep recognizes; help text and the private-graph label are accurate. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(storage): address third review round on the shared store (#3374) - clean --gc never collects a slot whose index lock is held: an analyze holds it until it registers the checkout, so a seeded but not yet registered slot is busy, not orphaned. - Reclaim compares absolute paths, so a relative GITNEXUS_HOME does not make live commit graphs look unreferenced. - graphPath is followed only into a published <commit>-<featureKey> dir, never .publish-* staging (TS and all three hook copies). - clean --gc fails on an unreadable stores root instead of reporting nothing to collect. - --no-share help states it is for opted-in clones. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(cli): land the English --no-share help and private-graph label (#3374) These two strings were meant fore2a9467andd746675but were left out of the commit; the zh-CN side landed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(storage): address fifth review round on the shared store (#3374) - analyze --no-share leaves the shared store even when routed to a branch sub-index (gate on sharedStore, not placement.branch) - clean --gc aborts a store whose member listing is unreadable instead of treating it as empty, and skips non-directory entries under stores/ - reusing an existing commit graph no longer fails a finished analyze when the redundant private graph cannot be wiped - a store pointer holding JSON null, a number, a string, or an array is treated as invalid - clean, remove, and DELETE /api/repo hold the checkout slot's index lock while deleting the slot - the server analyze launcher waits on the checkout slot the worker actually writes - sync the Factory hook copy; isolate hook tests from storage overrides; use a junction for the Windows worktree alias; the relative GITNEXUS_HOME test now sets a relative home Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(storage): address sixth review round on the shared store (#3374) - clean, remove, and analyze --no-share remove the checkout's store pointer while still holding the slot's index lock, so an analyze that takes the lock next cannot have its new pointer deleted - clean --gc does not follow a symlink under stores/ (lstat) - removing a store pointer keeps the checkout's .gitnexus directory when it cannot be listed, instead of treating it as empty Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(storage): address seventh review round on the shared store (#3374) - clean --gc aborts when the registry cannot be read instead of treating every member as orphaned; with no registry file it collects only members whose checkout directory is gone - seeding from a repository-local index re-reads its metadata under the index lock, so the copied graph and the saved metadata match - clean --gc skips a store another collector removed meanwhile, and reports "no shared stores" when stores/ holds only stray files Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(storage): address eighth review round on the shared store (#3374) - removing a checkout's storage unregisters it and removes its store pointer before deleting the slot directory, which the file lock backend uses for its lock file; withCheckoutSlotLock becomes removeCheckoutStorage, used by remove, clean, and DELETE /api/repo, and analyze --no-share follows the same order - the clean --gc preview skips a slot whose index lock is held, matching what --force would drop Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(storage): share the index store between clones automatically (#3352) Clones of one repository now share like linked worktrees. A clone whose normalized origin URL matches another registered, still-present clone joins that clone's store, or founds one (keyed on its own path) that the sibling joins on its next analyze. A lone clone keeps its own .gitnexus. Graphs stay keyed by commit and feature key, so clones only ever share a graph built from the same commit with the same settings. `analyze --no-share` now records a lasting opt-out (`shareOptOut` on the registry entry, preserved across re-registration); `--share-with` clears it. The analyze worker reports the storage it wrote over IPC so the server settles a clone's first shared slot. A query-time base-plus-overlay graph stays out: LadybugDB reads one database per query. Instead, private graph copies record whether the filesystem cloned them copy-on-write (sharing unchanged pages on disk) or made a full copy, and `status` reports it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(storage): never follow a symlinked checkout .gitnexus (#3374) A checkout whose `.gitnexus` is a symlink (e.g. `.gitnexus -> ..`) made the shared-store pointer helpers operate on whatever it pointed at: findLegacyLocalIndex listed the target as a "legacy index", so `clean --local-index --force` recursively deleted the checkout's siblings; writeSharedStorePointer wrote store.json/.gitignore there; and removeSharedStorePointer deleted store.json there and could rm -r the target directory. All four now go through probePointerDir, which lstats `.gitnexus` and accepts it only when it is a real directory whose realpath is realpath(checkout)/.gitnexus (mirroring stale-branch-slots.ts). Anything else is left untouched: no legacy index is reported or removed, and no pointer is written or removed. removeLegacyLocalIndex re-probes after sizing, just before its delete loop. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(storage): never share a graph from a checkout that hides files (#3374) Trigger: a sparse checkout, a skip-worktree/assume-unchanged entry, or an uninitialized submodule leaves `git status` clean, and the run's indexCoverage.dirtyPaths drops paths with no file hash, so publishSharedGraph published a graph missing those files as the commit graph. seedSharedSlot then copied the seed's lastCommit into the new pointer slot, so the next analyze of a sibling hit the up-to-date fast path without hashing. Fix: add isWorkingTreePristine (storage/git.ts): the unfiltered listWorkingTreeDirtyPaths must be empty (null fails closed) and every gitlink in the index must have a checked-out `.git`. publishSharedGraph requires it instead of isWorkingTreeDirty. seedSharedSlot keeps the seed's lastCommit only when the seed is at HEAD and the checkout is pristine, so a clean sibling still fast-paths onto the shared graph; any other seeded pointer gets an empty lastCommit, like seedFromLocalIndex, and its next run hash-diffs, then re-points at the commit graph on publish. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(storage): keep graphs with pending embeddings out of the shared store (#3374) Trigger: an analyze that finished with embeddings still owed (meta carries embeddingCheckpoint) was published as the commit graph, because the feature key ignores the checkpoint and publish never checked it. The published meta kept the checkpoint while the graph could never change. The next run healed the embeddings privately, found the commit graph already there, wiped its own healed graph and pointed back at the partial one. Fix: publishSharedGraph treats a checkpointed graph as not shareable, so it stays private and no published commit graph carries a checkpoint. When the target commit graph exists but records a checkpoint, has fewer stats.embeddings than the private graph, or has unreadable metadata, the checkout keeps its private graph instead of re-pointing. The commit graph is left as is because other checkouts may read it. Embeddings sync and the server embed job already privatize a pointer slot before writing (ensurePrivateSharedGraph). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(storage): rebuild a shared slot whose graph is missing (#3374) A publish interrupted between moving the checkout's graph into `.publish-<uuid>` staging and renaming staging onto the commit dir left slot metadata at HEAD with no graph and no graphPath; the next reclaim deleted the orphaned staging. The up-to-date fast path never checked that the graph exists, so every later analyze reported "Already up to date" over a checkout with no index. A failed restore in publishSharedGraph's catch had the same outcome, and also deleted the staging dir holding the only copy of the graph. - run-analyze: for a shared-store slot, stat the graph the checkout reads (resolveGraphPath for the flat slot, the branch slot's own lbug otherwise) before the fast path; if it is missing, force a full rebuild. An incremental run would diff nothing into a fresh, empty database. Private .gitnexus indexes are unchanged: they only lose their graph by hand, and metadata-only fast-path fixtures rely on that path. - publishSharedGraph: when moving the staged graph back fails and it is still in staging, keep the staging dir, log its path, and skip this run's reclaim, which would otherwise delete it. The next analyze finds no graph and rebuilds. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(storage): fail safe on unreadable slot metadata during reclaim (#3374) Trigger: reclaim counted commit-graph references with loadMeta, which returns null for a torn or unreadable gitnexus.json as well as for a missing one. A member whose metadata could not be read therefore "referenced nothing", and the graph it still used was deleted. Separately, in `clean --gc` an orphan slot that fs.rm could not delete threw out of reclaim, aborting the collection of every store after it. Fix: the reference loop reads slot metadata with a local loadMetaStrict. Only absent metadata (ENOENT/ENOTDIR, same legacy-mirror fallback as loadMeta) means no reference; any other read error or a parse failure throws with the file path, like listDirStrict. Every caller already treats a reclaim throw as best effort (analyze logs "skipped cleanup", slot removal ignores it) or surfaces it (clean --gc). A failed orphan slot delete is now kept: it stays a member, so the graph it names is kept too, and it is reported in ReclaimResult.keptMembers and by a new clean --gc line. loadMeta itself is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(server): unregister under the slot lock and answer 409 on DELETE /api/repo (#3374) DELETE /api/repo called removeCheckoutStorage(storagePath) with no unregister callback and no checkout path, swallowed every error, and unregistered later outside the slot lock. For a shared-store slot this left the checkout's store.json pointer behind, let an analyze re-register between the slot removal and the unregister, and reported {deleted} even when the slot lock could not be taken because an analyze held it. The handler now calls removeCheckoutStorage(storagePath, () => unregisterRepo(entry.path), entry.path), matching `gitnexus remove` and `gitnexus clean`, so the unregister and the pointer removal run under the slot lock; non-shared storage is still removed, then unregistered. The later standalone unregister is gone. An IndexLockTimeoutError answers 409 and leaves the entry registered; any other failure propagates to the handler's 500, also with the entry kept so the delete can be retried, instead of unregistering over leftover index files. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(storage): give concurrent sibling clones one deterministic founder key (#3374) Trigger: two registered clones of one origin, both still on local storage, analyzing at the same time each saw no sibling store yet and founded a store keyed on their own checkout path. registeredStore() then kept each clone in its own store for good, so they never shared a graph. Fix: when no sibling store exists, siblingCloneStore keys the new store on the canonical path that sorts first among this clone and its registered siblings (case-folded on Windows, like registryPathEquals), so every sibling computes the same key. An existing sibling store still wins. Clones that already diverged into two stores are not migrated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(storage): keep subdirectories of a clone out of shared stores (#3374) Trigger: `analyze --skip-git <clone>/pkg` inside a clone with a registered sibling of the same origin joined, or founded, a clone store. getRemoteUrl answers from any subdirectory, so siblingCloneStore saw the enclosing clone's remote. That broke the shared-store invariant that only tree roots participate. Fix: resolveOptedInStore now returns undefined for a path with no `.git` entry. It uses hasGitDir, which accepts a directory or a linked-worktree file, the same as resolveSharedStore's readCommonDir gate and run-analyze's repoHasGit. `--share-with` from such a path throws a clear error. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(hooks): prefix Windows device names with an extension in hook slot names (#3374) Trigger: on Windows, storage-slot.ts sanitizeSlotBasename prefixes a device-name basename that has an extension (`CON.txt` -> `repository-CON.txt`), but the hook's copy in registry-query.cjs only matched the bare device name. With GITNEXUS_STORAGE_ROOT set, a checkout named e.g. `con.txt` got a different slot from the hook than from the CLI, so the hook could not see its index. Fix: mirror the platform branch (extension form on win32, bare form elsewhere) in all four byte-identical registry-query.cjs copies, and point their header comments at storage-slot.ts. Add a hook-vs-TS parity test over device-name basenames on both stubbed platforms, and fix the byte-identity test title to say four copies. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * refactor(storage): tidy the shared-store fix series (#3374) Explain the keptStaging early return where it is checked, reuse the exported EmbeddingCheckpoint type in the publish test, and drop review-step labels from test comments. No behavior change. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(storage): address ninth review round on the shared store (#3374) - removeCheckoutStorage: keep the lock-safe order (unregister and drop the pointer under the slot lock, delete the slot last), but when the final rm fails, throw an error that says the checkout was already unregistered, names the leftover slot, and points at `gitnexus clean --gc --force`. - clean --gc preview no longer sweeps staging files: acquireIndexLock takes `sweep: false`, which the dry-run reclaim passes. - listStoreMetaRoots reports completeness; a store directory that cannot be listed (other than missing) makes the parse-cache prune retain every chunk instead of evicting keys other members still use. - clean.shared.kept says "could not be removed" rather than "still open". - ARCHITECTURE.md describes the stores/<key>/ layout and store.json pointer; README lists every GITNEXUS_SHARED_STORE off value and that it also stops clone sharing. - Tests: close the direct Ladybug connection in finally; fix a fixture JSDoc. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(storage): pin the publish gate for cone and sparse-index checkouts (#3374) isWorkingTreePristine already rejects every sparse mode, because each one marks left-out entries skip-worktree and `git ls-files -v` expands a sparse index. Only no-cone was tested; cover cone and cone with a sparse index too. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(storage): name the checks that answer recurring review findings (#3374) Comment-only. The review bot re-derives findings from the code on every push, so put the refuting fact next to each flagged line: the pristine check's hidden-path coverage, sparse checkouts being skip-worktree, the lock-safe unregister-then-delete order, the hook parity test for device names, and why the stores/ lstat is not a race guard. 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>
16 KiB
Last reviewed: 2026-09-24
Project: GitNexus · Environment: dev · Maintainer: repository maintainers (see GitHub)
Scope
| Boundary | Rule |
|---|---|
| Reads | gitnexus/, gitnexus-web/, eval/, plugin packages, .github/, .gitnexus/, docs. |
| Writes | Only paths required for the change; keep diffs minimal. Update lockfiles when deps change. |
| Executes | npm, npx, node under gitnexus/ and gitnexus-web/; uv run for Python under eval/; documented CI/dev workflows. |
| Off-limits | Real .env / secrets, production credentials, unrelated repos, destructive git ops without confirmation. |
Model Configuration
- Primary: Use a named model (e.g. Claude Sonnet 4.x). Avoid
Autoor unversionedlatestwhen reproducibility matters. - Notes: The GitNexus CLI indexer does not call an LLM.
Execution Sequence (complex tasks)
For multi-step work, state up front:
- Which rules in this file and GUARDRAILS.md apply (and any relevant Signs).
- Current Scope boundaries.
- Which validation commands you will run (
cd gitnexus && npm test,npx tsc --noEmit).
On long threads, "Remember: apply all AGENTS.md rules" re-weights these instructions against context dilution.
Claude Code hooks
PreToolUse hooks can block tools (e.g. git_commit) until checks pass. Adapt to this repo: cd gitnexus && npm test before commit.
Context budget
Commands and gotchas live under Repo reference below and in CONTRIBUTING.md. If always-on rules grow, split into .cursor/rules/*.mdc (globs). Cursor: project-wide rules in .cursor/index.mdc. Claude Code: load STANDARDS.md only when needed.
Reference docs
- ARCHITECTURE.md, CONTRIBUTING.md, GUARDRAILS.md
- Objective-C provider work: read docs/languages/objective-c-provider.md before changing Objective-C parsing or resolution.
- Call & inheritance resolution (RFC #909 Ring 3): See ARCHITECTURE.md § Scope-Resolution Pipeline. All languages resolve calls and inheritance through the scope-resolution pipeline (
Registry.lookup,preEmitInheritanceEdges,emitHeritageEdges,buildMro→MethodDispatchIndex). Shared code ingitnexus/src/core/ingestion/must not name languages — plug language behavior in viaLanguageProvider/ScopeResolverhooks. A language plugs in by implementingScopeResolver(scope-resolution/contract/scope-resolver.ts) and registering it inSCOPE_RESOLVERS. (The legacy call-resolution DAG +@heritagecapture path were removed in RING4-1 #942.) - Cursor:
.cursor/index.mdc(always-on);.cursor/rules/*.mdc(glob-scoped). Legacy.cursorrulesdeprecated. - GitNexus: standard skills in
.claude/skills/gitnexus-*/; MCP rules ingitnexus:startblock below.
PR Swarm Review (cross-CLI)
To run a production-readiness review of a GitNexus pull request from any AI CLI, follow
the canonical, CLI-neutral spec pr-swarm-review/orchestration.md
(seven read-only review personas under pr-swarm-review/personas/). It defines two
execution modes with the same output contract: Swarm mode (parallel subagents, e.g.
Claude Code) and Solo mode (one agent runs all lanes sequentially — Codex, Gemini,
Cursor, Copilot, or any agent reading this file). Per-CLI entrypoints are thin wrappers
listed in pr-swarm-review/README.md; edit review logic only
in the canonical files, never in the wrappers. The review is read-only — it never edits,
commits, or posts.
Engineering planning & execution (/gitnexus-plan · /gitnexus-work · /gitnexus-review · /gitnexus-lfg)
Four canonical, CLI-neutral skill specs under .claude/skills/ (Claude Code invokes
them as slash commands; Codex or any other agent reading this file should read the
named SKILL.md and follow it directly — user-level Codex prompts are documented in the
plan/work/lfg skill READMEs):
gitnexus-plan/SKILL.md— deep, implementation-ready plan for a code change: GitNexus graph intelligence for navigation, statement-level PDG slices for behavioral constraints, targeted source reads for verification. Output lands indocs/plans/with a reusable implementation context pack (section 11). Planning-only — it never edits code (index freshness refreshes viaanalyze --index-onlyare the one permitted state change). Interactive runs ask up front how deep to go (quick / standard / deep); Deepen mode strengthens an existing plan in place.gitnexus-work/SKILL.md— executes a gitnexus-plan as verified atomic commits: drift-checks the plan's evidence pin against HEAD,impactbefore every symbol edit, tests from the plan's scenarios,detect_changesbefore every commit.gitnexus-review/SKILL.md— read-only GitNexus review of a PR URL/number, branch or commit range, or local staged/unstaged/untracked changes. It pins exact SHAs, aligns the graph and checkout, runs a PDG-backed taint pass on trust-boundary diffs, scales to per-domain expert lenses from the graph's clusters (dispatched as parallel swarm lanes —ci-personas/— when the CI review agent runs it), and reports evidence-backed findings.gitnexus-lfg/SKILL.md— pipeline orchestrator: plan (depth asked up front) → blocking user gate (proceed or stop) → work →gitnexus-review.
The family ships with the npm package (gitnexus/skills/, installed to editor targets
by gitnexus setup) and the Claude Code plugin; review also has a standalone Cursor
mirror. gitnexus/test/unit/shipped-skills-sync.test.ts guards the copies. Token savings of the workflow are measurable with
eval/workflow_bench/ (real headless CLI runs, free-model routing supported — see its README).
Changelog
| Date | Version | Change |
|---|---|---|
| 2026-09-24 | 1.17.0 | Clones with the same origin URL now share a store automatically; --no-share records a lasting opt-out (#3352). |
| 2026-09-24 | 1.16.0 | Documented the shared worktree index store (<GITNEXUS_HOME>/stores/, analyze --share-with, GITNEXUS_SHARED_STORE=off) in the storage notes (#3352). |
| 2026-09-07 | 1.15.0 | Added the Objective-C provider guide as the required reference before changing Objective-C parsing or resolution. |
| 2026-07-20 | 1.14.0 | gitnexus-review gains a coordinated swarm: six ci-personas/ lanes the CI review agent dispatches as subagents (via the Agent tool), with a bounded critic gate and sidechain-excluded evidence. |
| 2026-07-16 | 1.13.0 | gitnexus-plan asks plan depth up front (quick/standard/deep) in interactive runs; gitnexus-lfg gate slimmed to proceed/stop (Deepen stays as the route-back mechanism). |
| 2026-07-16 | 1.12.0 | Renamed gitnexus-pr-review to gitnexus-review; added PR URL/number, branch/range, and local-change targets plus install migration (setup warns on a legacy gitnexus-pr-review dir and leaves it in place; uninstall removes it). |
| 2026-07-11 | 1.11.0 | Skill family shipped via npm skills/ + plugin (sync-guarded); added eval/workflow_bench token-savings benchmark. |
| 2026-07-11 | 1.10.0 | Added gitnexus-work (plan executor) and gitnexus-lfg (plan → deepen/work gate → review pipeline) skills; section renamed to Engineering planning & execution. |
| 2026-07-11 | 1.9.0 | Added Engineering planning (/gitnexus-plan) section; registered the gitnexus-plan skill (.claude/skills/gitnexus-plan/). |
| 2026-05-22 | 1.8.0 | Kotlin added to MIGRATED_LANGUAGES (registry-primary call resolution by default). Closes #1756 (companion-vs-instance dispatch) and #1757 (lambda scopes); refs #1746. RFC §6.4 corpus criterion waived (corpus-mode wiring is #927-scope); fixture criterion met. |
| 2026-04-23 | 1.7.0 | TypeScript added to MIGRATED_LANGUAGES (registry-primary call resolution by default). |
| 2026-04-20 | 1.6.0 | Added scope-resolution pipeline pointer (RFC #909 Ring 3); Python migrated to registry-primary. |
| 2026-04-19 | 1.5.0 | Cross-repo impact (#794): impact/query/context accept repo: "@<group>" + service. Removed group_query/group_contracts/group_status MCP tools; added gitnexus://group/{name}/contracts and gitnexus://group/{name}/status resources. |
| 2026-04-16 | 1.4.0 | Fixed: web UI description, pre-commit behavior, MCP tools (7->16), added gitnexus-shared, removed stale vite-plugin-wasm gotcha. |
| 2026-04-13 | 1.3.0 | Updated GitNexus index stats after DAG refactor. |
| 2026-03-24 | 1.2.0 | Fixed gitnexus:start block duplication. |
| 2026-03-23 | 1.1.0 | Updated agent instructions, references, Cursor layout. |
| 2026-03-22 | 1.0.0 | Initial structured header and changelog. |
GitNexus — Code Intelligence
This project is indexed by GitNexus as GitNexus (248612 symbols, 565510 relationships, 918 execution flows).
Index stale? Run
node .gitnexus/run.cjs analyze --index-onlyfrom the project root — it auto-selects an available runner. No.gitnexus/run.cjsyet? Bootstrap withnpx,bunx, orpnpm dlx— e.g.bunx gitnexus@latest analyze(npm 11 npx crash; #1939). On query/context/impact/cypher object results, read staleness.status and branch/lastCommit. Re-analyze only for behind or diverged — current is clone HEAD, not main.
Always Do
- MUST run impact analysis before editing. Use
impact({target: "symbolName", direction: "upstream"})(MCP) ornode .gitnexus/run.cjs impact "symbolName" --direction upstream --repo .(CLI fallback); report callers, processes, and risk. Never substitute grep for graph analysis. For unified PDG impact, addmode: "pdg"with optionalline: <N>— it returns statement-levelaffectedStatementsover CDG + REACHING_DEF and inter-procedural symbols ininterproceduralByDepth/byDepth; no-layer/degraded PDG results are UNKNOWN-risk notes (--pdglayer). CLI equivalent:node .gitnexus/run.cjs impact "symbolName" --direction upstream --mode pdg --line <N> --repo .. - MUST analyze graph changes before committing. Use
detect_changes({scope: "all"})(MCP) ornode .gitnexus/run.cjs detect-changes --scope all --repo .(CLI fallback).partial: trueortruncated: trueis not a clean check — a zero means unseen, not unaffected; re-run it. For regression review:detect_changes({scope: "compare", base_ref: "main"})ornode .gitnexus/run.cjs detect-changes --scope compare --base-ref "main" --repo .. - MUST warn on HIGH/CRITICAL
riskpre-edit; never useriskSharedAxesto waive a HIGH/CRITICALriskwarning. Compare File/symbol: MCP File omits axes; Graph-RAG expands File. - MUST treat
risk: UNKNOWNas unresolved, not as low. An empty caller set is not evidence the symbol is unused — it can also mean the callers are not resolvable by the index (plain-object property access, dynamic dispatch, cross-language calls).impactpairsUNKNOWNwith ariskNotesaying so. Confirm with a text search before treating the symbol as safe to change or delete; do not proceed on the strength of a zero. - MUST use
query({search_query: "concept"})for concepts/flows,context({name: "symbolName"})for a named symbol, orimpactfor blast radius, on read-only callers, dependencies, imports, or execution flow. Graph first; text search only for empty/UNKNOWN/literals. - For security review,
explain({target: "fileOrSymbol"})lists taint findings (source→sink flows; needsanalyze --pdg). - For control/data dependence,
pdg_query({mode: "controls", target: "fileOrSymbol"})answers "under what condition does X run?" (CDG, incl. guard clauses) andpdg_query({mode: "flows", target, variable})traces "where does variable Y flow?" (REACHING_DEF).--pdglayer.
Never Do
- NEVER edit a function, class, or method before MCP/CLI impact analysis.
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis, and never read
UNKNOWNas an all-clear — it means the walk could not answer, which is the one verdict that requires confirming by other means. - NEVER rename symbols with find-and-replace — use
renamewhich understands the call graph. - NEVER commit before MCP/CLI graph change analysis.
Resources
| Resource | Use for |
|---|---|
gitnexus://repo/GitNexus/context |
Codebase overview, check index freshness |
gitnexus://repo/GitNexus/clusters |
All functional areas |
gitnexus://repo/GitNexus/processes |
All execution flows |
gitnexus://repo/GitNexus/process/{name} |
Step-by-step execution trace |
CLI
| Task | Read this skill file |
|---|---|
| Understand architecture / "How does X work?" | .claude/skills/gitnexus-exploring/SKILL.md |
| Blast radius / "What breaks if I change X?" | .claude/skills/gitnexus-impact-analysis/SKILL.md |
| Trace bugs / "Why is X failing?" | .claude/skills/gitnexus-debugging/SKILL.md |
| Rename / extract / split / refactor | .claude/skills/gitnexus-refactoring/SKILL.md |
| Tools, resources, schema reference | .claude/skills/gitnexus-guide/SKILL.md |
| Index, status, clean, wiki CLI commands | .claude/skills/gitnexus-cli/SKILL.md |
Repo reference
Packages
| Package | Path | Purpose |
|---|---|---|
| CLI/Core | gitnexus/ |
TypeScript CLI, indexing pipeline, MCP server. Published to npm. |
| Web UI | gitnexus-web/ |
React/Vite thin client. All queries via gitnexus serve HTTP API. |
| Shared | gitnexus-shared/ |
Shared TypeScript types and constants. |
| Claude Plugin | gitnexus-claude-plugin/ |
Static config for Claude marketplace. |
| Cursor Integration | gitnexus-cursor-integration/ |
Static config for Cursor editor. |
| Eval | eval/ |
Python evaluation harness (Docker + LLM API keys). |
Running services
cd gitnexus && npm run dev # CLI: tsx watch mode
cd gitnexus-web && npm run dev # Web UI: Vite on port 5173
npx gitnexus serve # HTTP API on port 4747 (from any indexed repo)
Testing
CLI / Core (gitnexus/)
npm test— full vitest suite (~2000 tests)npm run test:unit— unit tests onlynpm run test:integration— integration (~1850 tests). LadybugDB file-locking tests may fail in containers (known env issue).npx tsc --noEmit— typecheck
Web UI (gitnexus-web/)
npm test— vitest (~200 tests)npm run test:e2e— Playwright (7 spec files; requiresgitnexus serve+npm run dev)npx tsc -b --noEmit— typecheck
Pre-commit hook (.husky/pre-commit): formatting (prettier via lint-staged) + typecheck for staged packages. Tests do not run in pre-commit — CI only.
Gotchas
npm installingitnexus/triggersprepare(builds viatsc) andpostinstall(build-tree-sitter-grammars.cjsactivates committed prebuilds in place undervendor/, and only source-builds when none matches). A C/C++ toolchain (python3,make,g++) is needed only for that source-build fallback.- The vendored grammars
tree-sitter-{c,dart,proto,swift,kotlin,zig}are handled uniformly: c is required; dart/proto/swift/kotlin/zig are optional and skippable viaGITNEXUS_SKIP_OPTIONAL_GRAMMARS=1. Install warnings appear only when no prebuild matches the platform-arch and no toolchain is present, and are non-fatal — only that language's parsing is unavailable. - ESLint configured via
eslint.config.mjs(TS, React Hooks, unused-imports). Nonpm run lintscript; usenpx eslint .. Prettier runs via lint-staged. CI checks both inci-quality.yml. - Index storage defaults to
<repo>/.gitnexus/.GITNEXUS_STORAGE_PATHselects one complete external index directory and wins overGITNEXUS_STORAGE_ROOT, which creates an isolated<repo-basename>-<12-hex>/slot per repository. Linked worktrees share one store under<GITNEXUS_HOME>/stores/<key>/(one immutable graph per commit, private graphs for checkouts with local changes, shared parse caches); clones with the sameoriginURL join a registered sibling's store automatically (analyze --share-withnames one,--no-shareopts out and is remembered), andGITNEXUS_SHARED_STORE=offor either storage env var disables sharing (#3352).GITNEXUS_CONTENT_RETENTIONisfull(default),symbol, ornone. MCPlist_repos,gitnexus://repo/{name}/context, and HTTPGET /api/repos/GET /api/repoexposestoragePath,contentRetention, andsourceAvailable. HTTP/api/fileand/api/grepreturn 410 unless retention isfull; MCPinclude_contentmay still return symbol spans atsymbol.