gitnexus 1.6.3-rc.29 → 1.6.3-rc.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,6 +15,22 @@
15
15
  * from the same Database is the officially supported concurrency pattern.
16
16
  */
17
17
  import lbug from '@ladybugdb/core';
18
+ /**
19
+ * Listeners notified when a pool entry is torn down (LRU eviction, idle
20
+ * timeout, explicit close). Used by upper layers (e.g. the BM25 search
21
+ * module) to invalidate per-repo caches that must not outlive the pool
22
+ * entry that produced them.
23
+ *
24
+ * Listeners run synchronously inside `closeOne` after the pool entry has
25
+ * been removed; throwing listeners are isolated so one bad listener does
26
+ * not prevent others from firing or break teardown.
27
+ */
28
+ type PoolCloseListener = (repoId: string) => void;
29
+ /**
30
+ * Subscribe to pool-close events. Returns a disposer that removes the
31
+ * listener (handy for tests).
32
+ */
33
+ export declare function addPoolCloseListener(listener: PoolCloseListener): () => void;
18
34
  /** Saved real stdout/stderr write — used to silence native module output without race conditions */
19
35
  export declare const realStdoutWrite: any;
20
36
  export declare const realStderrWrite: any;
@@ -74,3 +90,4 @@ export declare const isLbugReady: (repoId: string) => boolean;
74
90
  export declare const CYPHER_WRITE_RE: RegExp;
75
91
  /** Check if a Cypher query contains write operations */
76
92
  export declare function isWriteQuery(query: string): boolean;
93
+ export {};
@@ -17,6 +17,17 @@
17
17
  import fs from 'fs/promises';
18
18
  import lbug from '@ladybugdb/core';
19
19
  const pool = new Map();
20
+ const poolCloseListeners = new Set();
21
+ /**
22
+ * Subscribe to pool-close events. Returns a disposer that removes the
23
+ * listener (handy for tests).
24
+ */
25
+ export function addPoolCloseListener(listener) {
26
+ poolCloseListeners.add(listener);
27
+ return () => {
28
+ poolCloseListeners.delete(listener);
29
+ };
30
+ }
20
31
  const dbCache = new Map();
21
32
  /** Max repos in the pool (LRU eviction) */
22
33
  const MAX_POOL_SIZE = 5;
@@ -119,6 +130,16 @@ function closeOne(repoId) {
119
130
  }
120
131
  }
121
132
  pool.delete(repoId);
133
+ // Notify listeners AFTER the pool entry is gone so any cache-invalidation
134
+ // they perform is consistent with `isLbugReady(repoId) === false`.
135
+ for (const listener of poolCloseListeners) {
136
+ try {
137
+ listener(repoId);
138
+ }
139
+ catch {
140
+ // Isolate listener failures — teardown must complete.
141
+ }
142
+ }
122
143
  }
123
144
  /**
124
145
  * Create a new Connection from a repo's Database.
@@ -16,6 +16,17 @@ export interface BM25SearchResult {
16
16
  rank: number;
17
17
  nodeIds?: string[];
18
18
  }
19
+ /**
20
+ * Drop all ensured-FTS cache entries for a given repoId.
21
+ *
22
+ * Called from the pool-close listener so that a pool teardown / recreation
23
+ * forces the next `searchFTSFromLbug` call to re-issue `CREATE_FTS_INDEX`
24
+ * against the fresh connection rather than trust stale ensure-state from a
25
+ * previous pool lifetime.
26
+ *
27
+ * Exported for tests; the listener wiring is internal.
28
+ */
29
+ export declare function invalidateEnsuredFTSForRepo(repoId: string): void;
19
30
  /**
20
31
  * Search using LadybugDB's built-in FTS (always fresh, reads from disk)
21
32
  *
@@ -27,8 +27,45 @@ const FTS_INDEXES = [
27
27
  * Per-process cache for the MCP pool path: tracks which `(repoId, table)`
28
28
  * pairs have been ensured. The CLI/pipeline path gets its own cache inside
29
29
  * `lbug-adapter.ts` keyed by table/index, scoped to the singleton connection.
30
+ *
31
+ * IMPORTANT: an entry is added ONLY when the index was confirmed to exist
32
+ * (CREATE_FTS_INDEX succeeded, or failed with `'already exists'`). Other
33
+ * failures (transient lock errors, missing extension, etc.) leave the key
34
+ * unset so the next query retries instead of silently caching the failure.
35
+ *
36
+ * Entries for a given repoId are invalidated when its pool is closed —
37
+ * see the `addPoolCloseListener` registration in `searchFTSFromLbug`.
30
38
  */
31
39
  const ensuredPoolFTS = new Set();
40
+ /**
41
+ * Drop all ensured-FTS cache entries for a given repoId.
42
+ *
43
+ * Called from the pool-close listener so that a pool teardown / recreation
44
+ * forces the next `searchFTSFromLbug` call to re-issue `CREATE_FTS_INDEX`
45
+ * against the fresh connection rather than trust stale ensure-state from a
46
+ * previous pool lifetime.
47
+ *
48
+ * Exported for tests; the listener wiring is internal.
49
+ */
50
+ export function invalidateEnsuredFTSForRepo(repoId) {
51
+ const prefix = `${repoId}:`;
52
+ for (const key of ensuredPoolFTS) {
53
+ if (key.startsWith(prefix))
54
+ ensuredPoolFTS.delete(key);
55
+ }
56
+ }
57
+ /**
58
+ * Tracks whether we've already wired the pool-close listener for this
59
+ * process. The pool adapter is dynamically imported, so registration
60
+ * happens lazily on the first MCP-pool-backed FTS query.
61
+ */
62
+ let poolCloseListenerRegistered = false;
63
+ function registerPoolCloseListenerOnce(addPoolCloseListener) {
64
+ if (poolCloseListenerRegistered)
65
+ return;
66
+ poolCloseListenerRegistered = true;
67
+ addPoolCloseListener((repoId) => invalidateEnsuredFTSForRepo(repoId));
68
+ }
32
69
  async function ensureFTSIndexViaExecutor(executor, repoId, table, indexName, properties) {
33
70
  const key = `${repoId}:${table}:${indexName}`;
34
71
  if (ensuredPoolFTS.has(key))
@@ -36,17 +73,25 @@ async function ensureFTSIndexViaExecutor(executor, repoId, table, indexName, pro
36
73
  const propList = properties.map((p) => `'${p}'`).join(', ');
37
74
  try {
38
75
  await executor(`CALL CREATE_FTS_INDEX('${table}', '${indexName}', [${propList}], stemmer := 'porter')`);
76
+ // Index was created successfully — safe to cache.
77
+ ensuredPoolFTS.add(key);
39
78
  }
40
79
  catch (e) {
41
80
  // 'already exists' is the happy path (index persists on disk between
42
- // process invocations) — anything else we swallow because FTS is
43
- // best-effort: queryFTS itself returns [] on missing-index errors.
81
+ // process invocations) — cache it. Anything else is treated as a
82
+ // transient failure: surface a one-time warning and leave the key
83
+ // unset so the NEXT query retries rather than silently using a
84
+ // cached failure (which previously disabled BM25 for the whole
85
+ // process for that repo).
44
86
  const msg = String(e?.message ?? '');
45
- if (!msg.includes('already exists')) {
46
- // Best-effort — continue without index, queryFTS will fall back to [].
87
+ if (msg.includes('already exists')) {
88
+ ensuredPoolFTS.add(key);
89
+ }
90
+ else {
91
+ console.warn(`[gitnexus] FTS index ensure failed for repo "${repoId}" table "${table}" ` +
92
+ `(index "${indexName}"): ${msg || e}. Will retry on next query.`);
47
93
  }
48
94
  }
49
- ensuredPoolFTS.add(key);
50
95
  }
51
96
  /**
52
97
  * Execute a single FTS query via a custom executor (for MCP connection pool).
@@ -94,7 +139,13 @@ export const searchFTSFromLbug = async (query, limit = 20, repoId) => {
94
139
  // Use MCP connection pool via dynamic import
95
140
  // IMPORTANT: FTS queries run sequentially to avoid connection contention.
96
141
  // The MCP pool supports multiple connections, but FTS is best run serially.
97
- const { executeQuery } = await import('../lbug/pool-adapter.js');
142
+ const poolMod = await import('../lbug/pool-adapter.js');
143
+ const { executeQuery, addPoolCloseListener } = poolMod;
144
+ // Register the pool-close listener lazily on first use so a teardown of
145
+ // the pool entry (LRU eviction, idle timeout, explicit close) drops the
146
+ // matching `ensuredPoolFTS` entries. Without this, stale ensure-state
147
+ // can outlive the pool that produced it.
148
+ registerPoolCloseListenerOnce(addPoolCloseListener);
98
149
  const executor = (cypher) => executeQuery(repoId, cypher);
99
150
  // Lazy-create FTS indexes on first query for this repo (analyze no longer
100
151
  // creates them up-front, so we ensure them here). Cached per-process.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.3-rc.29",
3
+ "version": "1.6.3-rc.30",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",