GitNexus/GUARDRAILS.md

12 KiB

Guardrails — GitNexus

Rules for human contributors and AI agents. Complements AGENTS.md (workflows) and CONTRIBUTING.md (PR process).

Scope (least privilege)

  • Read: Source, tests, docs, public config as needed.
  • Write: Only files required for the fix or feature; no unrelated formatting or refactors.
  • Execute: Tests, typecheck, documented CLI commands. No destructive commands on user data without approval.
  • Off-limits: Other people's machines, production deployments you don't own, credentials you lack permission to use.

Maintainer may widen scope per task.


Non-negotiables

  1. Never commit secrets — API keys, tokens, real .env values, private URLs, session cookies. Use .env.example with placeholders.
  2. Never rename with find-and-replace in GitNexus-indexed projects — use rename with dry_run: true first (omitting dry_run also previews), review exact graph-backed semantic spans and coverage, then apply with rename and dry_run: false. Unsupported targets fail closed; inspect and refactor them manually with language-aware tools. No separate gitnexus rename CLI exists.
  3. Run impact analysis before editing shared symbols — impact (upstream) for functions/classes/methods others call. Do not ignore HIGH/CRITICAL without maintainer sign-off.
  4. Run detect_changes before commit — confirm diffs map to expected symbols/processes when the graph is available.
  5. Preserve embeddings — plain npx gitnexus analyze now preserves any embeddings recorded in the index metadata (.gitnexus/gitnexus.json, mirrored to the legacy meta.json) — the previous behavior wiped them. Use --embeddings to also generate vectors for new/changed nodes; use --drop-embeddings only when an explicit wipe is intended (e.g., model swap).
  6. Never terminate() a worker that may be inside a native call — killing a worker thread mid-N-API aborts the entire process (Napi::Error → std::terminate → SIGABRT, #2432), so a timeout meant to trigger a graceful fallback takes the whole run down instead. Any worker running native code (tree-sitter grammars, LadybugDB, Icebug) must either reach a JS-visible safe point first — the parse pool's shutdownDrainMs handshake in src/core/ingestion/workers/worker-pool.ts — or be abandoned with unref() and left to exit on its own. A one-shot worker that ends after a single postMessage needs no terminate() at all: it exits by itself. This bites hardest on the path you cannot test locally, because the abort only reproduces once the native module actually loads.

Signs (recurring failure patterns)

Format: Trigger → Instruction → Reason. Append new Signs when the same mistake repeats.

Stale graph after edits

  • Trigger: MCP warns index is behind HEAD, or search doesn't match latest commit.
  • Do: npx gitnexus analyze (plus --embeddings if used). Runs incrementally by default — the pipeline parses every file every run (cross-file resolution requires it), but tree-sitter dispatch is skipped for unchanged file chunks via the content-addressed cache, and only changed-file rows (plus their importers, transitively) are rewritten in LadybugDB. When the effective write set exceeds ~50% of the repo's files (minimum 50 files), the run transparently switches to the full wipe + bulk-COPY write plan and logs "switching to a full DB write" — expected behavior, not a bug, and file-level bookkeeping stays incremental. That same line also appears — regardless of write-set size, even for a one-file change — when a LadybugDB extension the existing index depends on cannot load on this machine (VECTOR, #2623; FTS, #2841), because a DB carrying those indexes refuses all row-level DML until the extension is loaded; run gitnexus doctor for live extension status and re-run with GITNEXUS_LBUG_EXTENSION_INSTALL=auto (with network access) to allow one bounded install attempt. The rebuild is one-shot: it clears the indexes, so the next run goes back to the incremental plan.
  • Why: Tools query LadybugDB from last analyze; git changes are invisible until re-indexed.

Index seems corrupt or "incremental" is misbehaving

  • Trigger: analyze produces unexpected results, or incrementalInProgress is set in the index metadata (.gitnexus/gitnexus.json / legacy meta.json), or the index is in a half-state after a crash.
  • Do: npx gitnexus analyze --force to rebuild the graph and FTS indexes. This may reuse unchanged parser output; when debugging parser/capture changes, use npx gitnexus analyze --no-parse-cache to rebuild that output too. The dirty-flag check forces the graph rebuild automatically when a previous incremental run didn't complete cleanly. A dirty-flag recovery rebuild parks the interrupted run's sidecars beside the DB as lbug.wal.dirty-recovery / lbug.shadow.dirty-recovery for post-mortem debugging — harmless, and removable with npx gitnexus clean --lbug-sidecars. Safe to delete the .gitnexus/parse-cache/ directory (and any legacy .gitnexus/parse-cache.json) at any time — content-addressed, will be regenerated.
  • Why: Incremental writeback is selective DB row replacement; if the on-disk state is inconsistent for any reason, a full rebuild is the cheapest path back to a known-good index.

Embeddings vanished after analyze

  • Trigger: Semantic search quality drops; stats.embeddings in the index metadata (gitnexus.json / legacy meta.json) is 0 after refresh.
  • Do: Re-run npx gitnexus analyze --embeddings to regenerate. Check the analyze log for a Warning: could not load cached embeddings line — if present, the cache restore failed (corrupt DB / schema mismatch) and the rebuild had nothing to preserve. If you intentionally passed --drop-embeddings, this is expected.
  • Why: Plain analyze preserves prior vectors by re-inserting them after the rebuild; ways to end up at zero include an explicit --drop-embeddings, a cache-load failure (now logged), or a model/dimension change that invalidates the cache — but zero is no longer the only embedding-loss signature to watch for; see the Sign below for the non-zero, partial-failure case. A dirty-recovery run that cannot move the crashed WAL aside now either discards it (logged: forensics lost, embeddings still preserved) or fails fast with a lock error naming the holder — it never silently zeroes embeddings.

Analyze finishes but embeddings are incomplete (partial embedding index)

  • Trigger: npx gitnexus status reports incompleteReasons: ["embedding-checkpoint-pending"] (or the human-readable "Index incomplete reasons" line); stats.embeddings is honest and non-zero, and the preceding analyze log showed a Warning: N node(s) lost their embeddings to embedding-endpoint failures line (#2790).
  • Do: Re-run plain npx gitnexus analyze — no --embeddings flag needed. A retained embeddingCheckpoint in the index metadata forces embedding generation for exactly the pending nodes regardless of flags, and clears once they succeed. --drop-embeddings abandons the pending nodes instead of retrying them; --force also discards the checkpoint (with a warning) and rebuilds without resuming it.
  • Why: A long analyze run against a flaky HTTP embedding endpoint tolerates bounded sub-batch failures instead of aborting the whole run: it deletes the affected nodes' embedding rows (so they hold zero rows, never a partial set) and records those nodes as pending in embeddingCheckpoint. stats.embeddings stays an honest, non-zero count of everything that did succeed, so this state never trips the "Embeddings vanished" Sign above — embedding-checkpoint-pending is the only reliable signal.

Scope extraction is incomplete

  • Trigger: npx gitnexus status reports incompleteReasons: ["scope-extraction-failed"] when files were omitted, or incompleteReasons: ["scope-extraction-unverified"] when the index predates the completeness receipt or its metadata is unreadable. impact/context reports the same uncertainty as epistemic: "lower-bound"; confirmed omissions set causes.scopeExtractionFiles > 0.
  • Do: Re-run npx gitnexus analyze (--force for a full graph rebuild). If the reason persists, inspect the scope-extraction warnings and treat impact counts as floors until the affected source is supported or corrected.
  • Why: Parsing continued, but scope captures for the reported file count could not be produced even after the main-thread fallback. Calls, inheritance, imports, or accesses originating there may therefore be absent from the graph.

Analyze reports INCOMPLETE with a collapsed graph write

  • Trigger: Analysis rejects a staged graph write with expected and persisted relationship counts, or an in-place run prints Repository indexed INCOMPLETELY, exits nonzero, and records incompleteReasons: ["graph-write-collapsed"].
  • Do: Inspect COPY errors, available memory, and free disk space on the volume holding .gitnexus/; confirm no second analyze is running against the same repo, then run npx gitnexus doctor. For native buffer-pool exhaustion, see RUNBOOK.md for GITNEXUS_LBUG_BUFFER_POOL_SIZE and memory-budget guidance. Re-run npx gitnexus analyze --force after addressing the cause.
  • Why: Far fewer relationships are readable back than the pipeline produced. A staged build throws before publication and preserves the previous index, if any. In-place writes cannot roll back, so the incomplete stamp and nonzero exit prevent a partial graph from being presented as successful. Recognized buffer exhaustion also throws immediately during COPY or fallback inserts. The collapse check compares in-memory totals (including streamed rows) against the post-write count, stays unmeasurable when the count cannot be read, and is skipped on incremental runs where whole-scope counts are not comparable.

MCP lists no repos

  • Trigger: MCP stderr says no indexed repos.
  • Do: npx gitnexus analyze in the target repo; verify npx gitnexus list shows it.
  • Why: MCP discovers repos via ~/.gitnexus/registry.json, populated by analyze.

Wrong repo in multi-repo setups

  • Trigger: Query/impact results belong to another project.
  • Do: Confirm an MCP default is configured or the GitNexus process was launched inside the intended registered path without crossing into an unindexed nested Git checkout. Otherwise call list_repos, then pass repo on subsequent tools; pass it for mutating tools when multiple repos are registered and no MCP default exists.
  • Why: Read-only tools derive their default from MCP configuration or a process cwd that stays within one registered Git boundary. Outside those paths the target remains ambiguous, and mutating tools stay explicit unless configuration supplies the target.

LadybugDB lock / "database busy"

  • Trigger: Errors opening .gitnexus/lbug while MCP and analyze both run.
  • Do: Stop overlapping processes (one writer at a time). Retry analyze or restart MCP.
  • Why: Embedded DB expects single-process ownership. @ladybugdb/core 0.18.0 also reports this contention as "Only one write transaction at a time is allowed in the system." — our busy/lock retry matcher (isDbBusyError in src/core/lbug/lbug-config.ts) recognizes this exact string too, so it's auto-retried the same as any other lock error. If you see that exact message, it's the same "one writer at a time" issue above, not a new failure mode.

Publishing & supply chain

  • npm: Do not publish from unreviewed automation. Bump version intentionally; tag releases to match package.json.
  • Dependencies: Minimal, auditable package.json changes; run tests and CI after lockfile updates.
  • License: PolyForm Noncommercial 1.0.0 — do not relicense without maintainer approval.

Escalation

Stop and ask a human maintainer when:

  • Impact analysis shows HIGH/CRITICAL risk and the task still requires the change.
  • You need to alter CI, release, or security-sensitive config.
  • Requirements conflict (e.g. "speed up analyze" vs "must keep all embeddings on huge repo").
  • You are unsure whether data loss is acceptable (clean, forced migrations, schema changes).