mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-07 08:26:11 +00:00
* fix(hook): resolve canonical repo root + guard read-only FTS ensure (#1224) Two bugs in the Claude Code hook + query layer integration: 1. `findGitNexusDir` (in `gitnexus/hooks/claude/gitnexus-hook.cjs` and `gitnexus-claude-plugin/hooks/gitnexus-hook.js`) walked upward from cwd looking for a non-registry `.gitnexus/`. In linked git worktrees created via `git worktree add`, the canonical repo's `.gitnexus/` never sits above the worktree path, so the walk silently fails and neither augmentation nor staleness notifications fire. Fix: keep the cwd-walk as the fast path, then fall back to `git rev-parse --git-common-dir` to resolve the shared `.git/` directory (which lives inside the canonical repo across all linked worktrees) and walk up from its parent. Returns null cleanly when `git` isn't on PATH or cwd isn't inside any working tree. 2. `ensureFTSIndex` in the LadybugDB adapter rethrew when the active connection is read-only (e.g. the MCP query pool, which opens DBs read-only by design). Defensive callers used to surface five "Cannot execute write operations in a read-only database" warnings per query. Fix: extract `isReadOnlyDbError` (mirroring the existing `isDbBusyError` discriminator) and have `ensureFTSIndex` catch the read-only error, cache the key, and return silently. Index creation is owned by `gitnexus analyze` on a writable connection — the ensure call is safely a no-op on the read pool. Lock / busy / "already exists" / schema errors continue to propagate. Tests: - `test/unit/hooks.test.ts`: new "Linked git worktree resolution" block exercises both hooks against a real linked worktree to confirm PostToolUse stale notifications fire, plus a negative case when the canonical repo has no `.gitnexus/`. - `test/unit/lbug-readonly-error.test.ts`: new file unit-tests the `isReadOnlyDbError` discriminator (positive matches, case insensitivity, non-Error inputs, and unrelated errors that must still surface — lock contention, "already exists", schema misses). - `test/integration/lbug-core-adapter.test.ts`: extends the existing FTS coverage with an idempotency assertion for `ensureFTSIndex` to pin the read-only guard's success-path contract. Verified with `npx tsc --noEmit` and `vitest run` on the affected files (hooks + readonly + lbug-core-adapter + bm25-search + lbug-extension-loader + lbug-embedding-hashes — 136 tests pass). Build: `npm run build` succeeds. Closes #1224 * fix(local-backend): cover supported vector path Add the supported-platform regression assertion for QUERY_VECTOR_INDEX and align the unsupported VECTOR diagnostic wording with platform policy. Made-with: Cursor --------- Co-authored-by: Gergo Magyar <gergomagyar@icloud.com>
55 lines
2.4 KiB
TypeScript
55 lines
2.4 KiB
TypeScript
/**
|
|
* Regression Tests: read-only DB error discriminator (#1224)
|
|
*
|
|
* The MCP query pool opens LadybugDB read-only. Defensive callers of
|
|
* `ensureFTSIndex` from that pool used to spam stderr with five
|
|
* "Cannot execute write operations in a read-only database" warnings
|
|
* per query because the cache was invalidated each time. The fix:
|
|
* `ensureFTSIndex` now treats the read-only error as a no-op and
|
|
* caches the key — but to do that it relies on a precise discriminator
|
|
* that does NOT swallow lock / busy / "already exists" errors.
|
|
*
|
|
* This file unit-tests the discriminator directly so future refactors
|
|
* keep the contract.
|
|
*/
|
|
import { describe, it, expect } from 'vitest';
|
|
import { isReadOnlyDbError } from '../../src/core/lbug/lbug-adapter.js';
|
|
|
|
describe('isReadOnlyDbError', () => {
|
|
it('matches the canonical LadybugDB read-only message verbatim', () => {
|
|
const err = new Error(
|
|
'Connection exception: Cannot execute write operations in a read-only database!',
|
|
);
|
|
expect(isReadOnlyDbError(err)).toBe(true);
|
|
});
|
|
|
|
it('matches when the error is wrapped in additional prefix text', () => {
|
|
const err = new Error(
|
|
'Runtime exception: Cannot execute write operations in a read-only database',
|
|
);
|
|
expect(isReadOnlyDbError(err)).toBe(true);
|
|
});
|
|
|
|
it('is case-insensitive on the "read-only" substring', () => {
|
|
expect(isReadOnlyDbError(new Error('Read-Only Database access denied'))).toBe(true);
|
|
});
|
|
|
|
it('accepts non-Error values (string, unknown) without throwing', () => {
|
|
expect(isReadOnlyDbError('write rejected: read-only database')).toBe(true);
|
|
expect(isReadOnlyDbError({ toString: () => 'read-only database' })).toBe(true);
|
|
expect(isReadOnlyDbError(null)).toBe(false);
|
|
expect(isReadOnlyDbError(undefined)).toBe(false);
|
|
});
|
|
|
|
it('does NOT match unrelated errors that the ensure path must still surface', () => {
|
|
// Lock contention — handled separately by isDbBusyError; must not be
|
|
// silenced by the read-only filter.
|
|
expect(isReadOnlyDbError(new Error('Could not set lock on file'))).toBe(false);
|
|
// "already exists" — the happy idempotent path inside createFTSIndex.
|
|
expect(isReadOnlyDbError(new Error('Index file_fts already exists'))).toBe(false);
|
|
// Schema-level problem.
|
|
expect(isReadOnlyDbError(new Error('Table File does not exist'))).toBe(false);
|
|
// Generic transient error.
|
|
expect(isReadOnlyDbError(new Error('Connection refused'))).toBe(false);
|
|
});
|
|
});
|