* feat(storage): add configurable index storage and content retention tiers
Rebase #3060 onto current origin/main. Keep GITNEXUS_STORAGE_PATH,
GITNEXUS_STORAGE_ROOT, and GITNEXUS_CONTENT_RETENTION, and fold in
main's FTS skip, embed-session, and help-text updates.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Address PR review feedback (#3060)
Keep legacy registry rows on the local storage fallback, resolve
symlinks before the destructive-path guard, and align hook lookup
with CLI branch slugs, branch-slot metadata, and longest-path match.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Address PR review feedback (#3060)
Only list swept upload directories after a successful removal so
callers cannot treat a permission or transient rm failure as gone.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Address PR review feedback (#3060)
Document that getStoragePath may consult registered storage while
this module still does not mutate the global registry.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(storage): close review findings for external indexes and retention
Re-inspect ownership under the analyze lock, fail-closed when the
registry file is missing, and keep skip-git hook discovery plus
retention fields on HTTP/MCP list surfaces. /api/file stays 410
unless contentRetention is full.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore(autofix): apply prettier + eslint fixes via /autofix command
* Address PR review feedback (#3060)
Treat lock-only index dirs as empty, honor HTTP --force storage policy, and prefer registered plus branch-aware slots in hooks and augment.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Address PR review feedback (#3060)
Keep hook fallbacks inside the current worktree, compare foreign-local slots canonically, and make storage fixtures survive ownership validation.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix macOS hook test expecting realpath'd registry paths.
resolveHookRepo returns the written registry path, not a filesystem realpath, so the assertion must match that.
* Address gitnexus-check warnings on hook install docs and slot tests.
The Cursor troubleshooting list omitted registry-query.cjs, and the writable-slot test only checked that isDirectory exists instead of that the path is a directory.
* Align the HTTP catalog source-scan with skippable resolveRepo validation.
resolveRepo lists fresh repos with validate: options.validateStorage !== false so DELETE can skip prune; the test still required a literal validate: true.
* Harden storage path sinks so CodeQL path-injection and ReDoS alerts clear.
Contain every filesystem probe inside the resolved storage slot with the inline path.relative idiom, reject filesystem-root slots, and trim slot basenames in linear time.
* Settle bridge stamps before writing so CI size/mtime matches stay stable.
LadybugDB can still flush into bridge.lbug after close+rename; persist whole-millisecond mtimes and wait for consecutive stats to agree so a freshly written pair matches.
* Type the settled bridge stat as fs.Stats so tsc does not see bigint.
Awaited<ReturnType<typeof fsp.stat>> collapsed the bigint overload and broke prepare/typecheck on CI.
* Keep the bridge mtime stamp exact so same-size swaps still fail the pair check.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Wrap the bridge stamp predicate so prettier --check stays green.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Require a quiet interval before stamping a settled bridge file.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reuse shared storage and settle helpers instead of local copies.
Co-authored-by: Cursor <cursoragent@cursor.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>
`gitnexus status` reported "stale (re-run gitnexus analyze)" whenever the
working tree held any modified or untracked file, including files the index
never reads. Because `analyze` cannot commit, stash or delete such a file,
the remedy it prescribed could not clear the verdict — the only way back to
up-to-date was to remove the file.
`meta.fileHashes` already records the exact set of files a run covered, so
answer the question directly: compare those hashes against disk, reusing
analyze's own scan, hash and diff helpers so the two cannot disagree about
what "changed" means. A new coverable file still counts as stale (the index
is genuinely incomplete then), but one `analyze` now settles it. The
repo-wide dirty flag survives only as the fallback for metadata written
before `fileHashes` existed.
Both freshness checks now read GitNexus's own analyze output (AGENTS.md,
CLAUDE.md, the agent skill mirrors) from one shared list. They previously
held separate copies, and since analyze rewrites those files after
recording hashes, a per-file comparison that missed them would report a
freshly indexed repository as permanently stale.
Closes#3077
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>