feat: multi-branch indexing and branch-scoped querying (#2106)#2137
Conversation
- guard analyze against --branch != checked-out branch (prevents writing one branch's working tree into another branch's index slot) - fix branch-handle pool reinit thrash (track observed indexedAt by lbugPath, since applyBranchScope returns fresh handles) - remove dead resolveRefToCommit helper (staleness uses HEAD vs branch meta) - RepoListing.branches -> Omit<BranchSummary,'stats'> for type cohesion - add tests: branchSlug traversal containment, --branch mismatch reject, callTool branch threading, legacy-entry branch routing, status detached/stale
|
@magyargergo is attempting to deploy a commit to the NexusCore Team on Vercel. A member of the Team first needs to authorize it. |
- P1 data-loss: a detached-HEAD re-analyze (CI's actions/checkout default) no longer strips the primary's meta.branch stamp; preserve it so a later branch analyze cannot claim & overwrite the flat/primary index. +cascade integration test - P2: capture validateBranchName's trimmed return for --branch so a whitespace-padded value no longer false-rejects on-branch or ghosts an index - F1: on a lost/rebuilt registry, a branch run reconstructs the primary top-level entry from the flat meta, not the feature branch's meta
magyargergo
left a comment
There was a problem hiding this comment.
Tri-review digest (3 methods)
Methods: GitNexus swarm + Compound-Engineering personas + Codex. Engine breakdown: 6 Claude lanes (risk, test/CI, security-boundary, correctness, adversarial, performance) + Codex — the one independent engine, live. Reviewed the post-fix diff (after main was merged in). The 3 actionable findings below were found AND fixed in this session (commit 62bb93e0) with passing tests — this is a record + residuals, not open blockers.
🔴 P1 — data-loss on detached-HEAD re-analyze (FIXED)
A detached-HEAD re-analyze (exactly what CI's actions/checkout does by default) stripped the primary's meta.branch stamp; a later branch analyze then saw an "unowned" flat slot and overwrote the primary index.
- Trigger: index
main→ indexfeature→ detached re-analyze (stamp wiped) → indexfeature→main's graph destroyed. - Found by the adversarial lane, corroborated by Codex on the same
meta.branchstamping (Codex+Claude = the strong signal). - Fix: preserve an existing stamp when the label is null —
branch: branchLabel ?? existingMeta?.branch— plus a cascade integration test that reproduces and pins it.[reproduced]
🟠 P2 — --branch whitespace (FIXED)
validateBranchName trims but its return was discarded; a padded --branch " feature" false-rejected while on-branch and ghosted an index when detached. Found by correctness + adversarial (consistent across Claude personas). Fix: forward the trimmed return. [code-read]
🟠 P2 — registry-loss seed (FIXED)
If registry.json is lost/rebuilt, a branch run seeded the top-level entry from the feature branch's meta, so --branch <primary> could not resolve. Codex (HIGH) + correctness. Fix: reconstruct the primary top-level from the flat meta when no entry exists. [code-read]
✅ Validated correct (credit to the change)
The reviews actively refuted several suspected issues:
- Path-traversal via branch names is contained —
branchSlug=sanitizeRepoName(ref)+ sha256 of the raw ref; security verified ~19 traversal payloads + a containment test. - The normal two-branch sequence does not overwrite the primary (integration test).
- The pool reinit/staleness fix is correct —
lastObservedIndexedAtstays in lockstep withinitializedRepos; real rebuilds are still detected. applyBranchScopeuses exact===(no substring/prefix mis-resolution);getCurrentBranchhas no injection.- Test/CI lane rated the coverage "superior to a typical feature PR."
🔵 Deferred residuals (documented, non-blocking)
- P3 — AGENTS.md fast-path (Codex): a non-primary branch's already-up-to-date analyze can still touch
AGENTS.mdviarefreshBaseRefLine(the gate lives inrunFullAnalysis, not the CLI fast path —src/cli/analyze.ts). NeedsrunFullAnalysisto surface the placement. - Primary-inversion (first-indexed non-default branch claims the flat slot); cross-branch shared-cache prune churn (perf only — content-addressed → correct);
branches[]unbounded growth / noclean --branch; route the auto-detected branch label throughsanitizeDetectedBranch; branch-pool eviction onclean/unregister (bounded, correctness-safe). All noted in the PR description.
CI
Running on the pushed branch; the Vercel "fail" is fork deploy-auth, not code. Locally: tsc clean, full touched-area unit suite + 2 integration tests green.
Independence note: two of three methods are Claude under different persona prompts (correlated priors); only Codex is a different engine. The strong signals are Codex+Claude agreement — persona-only agreement is "consistent," not independent confirmation.
Automated multi-tool digest — verify before acting.
CI Report✅ All checks passed Pipeline Status
Test Results
✅ All 10951 tests passed 16 test(s) skipped — expand for details
Code CoverageTests
📋 View full run · Generated by CI |
Summary
Closes #2106. Makes GitNexus indexing branch-aware. Today, re-analyzing after a
git checkoutsilently destroys the previous branch's graph (the single.gitnexus/lbugis wiped and rebuilt). This PR gives each branch its own indexwhile keeping single-branch behavior backward compatible.
is indexed into
<repo>/.gitnexus/branches/<slug>/(its own LadybugDB). Theprimary (first-indexed) branch keeps the flat
.gitnexus/{lbug,meta.json}layout.
git checkout feature && gitnexus analyzeno longer clobbersmain's index.branchparameter on the MCP tools(
query,cypher,context,impact,detect_changes,rename,route_map,tool_map,shape_check,api_impact) and a--branchflag onthe CLI (
analyze,query,context,impact,cypher,detect-changes).It swaps the resolved
lbugPath; the connection pool already keys bylbugPath, so per-branch DBs isolate with zero pool-layer changes.branch?/branches?[]on the one entry per path;listandstatussurface branch indexes;
list_reposadvertises abranchessub-field.Design notes
analyzepath is now branch-aware.A single-branch user who never switches branches between analyses sees zero
change (flat layout, no
branchfield for detached HEAD / non-git). A userwho switches branches now gets separate, non-destructive indexes — which is
exactly what Support multi-branch indexing and cross-branch search #2106 asks for.
node-PK change (each branch has its own DB) and no
INCREMENTAL_SCHEMA_VERSIONbump. Existing indexes need no re-analyze.--default-branch: the existing--default-branchflag isthe cosmetic base_ref feature (generated AGENTS.md/CLAUDE.md). The new
--branchselects the index slot and does not read the.gitnexusrcbranchkey.
HEAD).sanitizeRepoName(ref)+ a short sha256 of the raw ref,so
feature/xandfeature_xcan never collide; path-traversal is contained(verified against adversarial inputs).
analyze --branch Xis refused whenXis not the checked-outbranch — analyze indexes the working tree, so labeling tree Y as branch X
would corrupt X's index.
Scope deferred to follow-ups (intentional)
makes this a fan-out via the existing GroupService RRF.
/api) branch parity — MCP + CLI ship here; REST is a fast-follow.clean --branch, orphan reaping, per-repo cap).Test plan
getCurrentBranch;branchSlug(collision + traversalcontainment);
resolveBranchPlacement(all precedence branches); registrybranch-nesting; tool-schema branch param; MCP
resolveRepo/callToolbranchrouting (incl. legacy-entry + un-indexed-branch error); CLI
--branchhelp +i18n;
list/statusbranch rendering (incl. detached HEAD + stale).multi-branch-analyze.test.ts): full two-branchanalyze proving the feature index coexists with an untouched primary, and the
registry nests the branch.
tscclean;gitnexus/CHANGELOG.mduntouched (release-owned).Known limitation
~/.gitnexus/registry.jsonis global: an older GitNexus binary thatupserts a path entry will drop the
branches[]it doesn't understand. Newfields are otherwise ignored by old binaries (additive).
Residual Review Findings
The pre-merge multi-persona review (correctness/adversarial/security/api-contract/
reliability/maintainability/testing/project-standards) surfaced these deferred,
non-blocking items (the actionable P1s were fixed in-PR):
one makes that branch own the flat slot; a later
mainanalyze is routed to asub-dir. Deliberate (first-indexed claims primary;
origin/HEADis unreliablein CI/clones) and non-destructive — both indexes coexist. Follow-up: optionally
let a default-branch analyze reclaim the flat slot.
parse-cache/and durable parsedfile store are pruned to the current run's keys, so
alternating analyze between two large divergent branches re-parses on each
switch. Correctness-safe (content-addressed); follow-up: union live keys across
branches or per-branch shards.
branches[]andbranches/<slug>/dirs are never pruned when a branch is deleted. Follow-up:gitnexus clean --branch/ TTL / cap (the deferred branch-GC work).that
validateBranchNamewould reject (exotic refs) is stored verbatim, so alater validated
--branchlookup may not round-trip. Follow-up: route theauto-detected label through
sanitizeDetectedBranch.repo-manager.tssize. Pre-existing >1k-line file; the new branchprimitives could later extract to
branch-index.ts.🤖 Generated with Claude Code
✅ Residual review findings — all resolved (follow-up commits)
The deferred residuals (from the autofix review + Codex tri-review) were planned
(
docs/plans/2026-06-10-002-…), pressure-tested by a deepening pass, and fixedone commit per finding:
flatMeta.branch(corrupt-meta misroute guard)--branch <primary>on a legacy unstamped flat indexgitnexus clean --branch <name>(resolves via the recordedbranches[]summary)clean --branchcacheKeys+ union them at prune (fail-safe-toward-retention) — a branch switch no longer evicts another branch's shardssanitizeDetectedBranchwriteRegistry(tmp+rename) + re-read-before-write to narrow the registry racesrc/storage/branch-index.tsThe deepening caught a non-implementable R6 design (chunk keys aren't in
meta.json) and a disproportionate R8 (live-DB move) before any code was written. All changes additive;INCREMENTAL_SCHEMA_VERSIONunchanged; single-branch behavior backward compatible;gitnexus/CHANGELOG.mduntouched.