* fix(cli): surface silent finalize-skips so analyze cannot exit 0 without persisting (#1169)
Closes#1169.
On Windows, `gitnexus analyze .` was observed to exit with code 0 after
printing only the "GitNexus Analyzer" banner. `.gitnexus/lbug.wal` was
written but `meta.json` was never persisted and the repo was not added
to `~/.gitnexus/registry.json`, so `gitnexus list` / `status` reported
no indexed repository. The reporter confirmed the same shape on both
LadybugDB (1.6.x) and the pre-LadybugDB KuzuDB build (1.4.1), so the
silent finalize-skip is upstream of the DB engine and indistinguishable
from a healthy index from the user's perspective.
This change makes that state a hard, actionable failure regardless of
the upstream root cause.
Behaviour change
- New `assertAnalysisFinalized()` invariant in `repo-manager.ts` checks
that meta.json exists at `<repo>/.gitnexus/meta.json` AND that the
global registry has a canonical-path-matching entry. Throws
`AnalysisNotFinalizedError` (kind: "AnalysisNotFinalizedError") with a
diagnostic that names the missing artifact and the storage path the
user should inspect.
- `analyzeCommand` invokes the invariant on the rebuild path (skipped
on `alreadyUpToDate`), so a future silent finalize-skip surfaces with
exit code 1 and a recoverable error instead of a silent exit 0.
- `analyzeCommand` installs idempotent `unhandledRejection` and
`uncaughtException` handlers that bypass the progress bar's console
redirection by writing to a stderr handle captured at module load.
This addresses the secondary symptom where the `barLog` redirection
visually erased stack traces with `\x1b[2K\r` and stripped them via
`String(err)`.
- The catch block also writes the failing error's full stack via the
captured stderr, so failure diagnostics survive any downstream
monkey-patching of `process.stdout`/`stderr`.
Tests
- `test/unit/repo-manager-finalize-invariant.test.ts` (4 tests): cover
both `missing="meta"` and `missing="registry-entry"`, the happy path,
and Windows case-insensitive registry path matching.
- `test/integration/cli-e2e.test.ts` adds a regression test that runs
the real CLI on a fresh repo copy, asserts exit 0, AND verifies
`meta.json` plus the matching registry entry are both written —
catches any future regression of the wiring.
Validation
- `npx tsc --noEmit` passes.
- `npx vitest run --project default` passes for all my touched files
(89 tests across 4 files). The full default suite reports 7188 pass
with the known native LadybugDB Windows-worker flake unrelated to
this change.
- `npx prettier --check` clean on the diff.
- `npx eslint` reports only pre-existing `any` warnings on the file;
no new warnings introduced.
- Live repro on the issue's two-file Python fixture reproduces a
successful index after the change: meta.json present (742 B), exit 0,
`gitnexus list` shows the repo.
Rollback
Strictly additive — the success path is unchanged when `meta.json` is
written and the registry is updated. Reverting the four-file diff is
safe; the previous silent-finalize behaviour returns. No persisted
schema or registry shape changes.
DoD
- [x] Runtime wiring is complete on the affected CLI path.
- [x] Requested behavior is correct and existing contracts are preserved.
- [x] Smallest correct solution — one invariant, one helper, two
handlers; no speculative abstraction.
- [x] Tests prove the changed behavior at unit AND integration level.
- [x] Required validation for `gitnexus/` was run.
- [x] Repo boundaries respected; no language-specific code, no shared
ingestion changes, no new injection surfaces.
- [x] Diff contains only the intended change — no unrelated churn.
Made-with: Cursor
* fix(cli): enforce analyze finalization on fast path (#1169)
Address PR review feedback by checking finalization even when analyze reports already up to date, and by making the #1169 E2E guard fail on timeout instead of passing silently.
Made-with: Cursor
* test(cli): fix#1169 regression coverage on CI
Normalize macOS temp paths in the registry assertion and update the analyze worker timeout test mock for the new finalization invariant exports.
Made-with: Cursor