gitnexus 1.6.9-rc.21 → 1.6.9-rc.22

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.
@@ -353,6 +353,20 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
353
353
  }
354
354
  try {
355
355
  await initLbug(lbugPath);
356
+ // Gate on FTS availability BEFORE touching any index. createSearchFTSIndexes
357
+ // now DROPs each index before recreating it (so schema changes reach existing
358
+ // DBs); if the extension were unavailable, the drops would run and leave the
359
+ // DB index-less, only failing at the create step. Fail loudly first — mirrors
360
+ // the analyze path's `if (ftsAvailable)` gate below — so an unavailable
361
+ // extension never destroys the existing indexes.
362
+ const repairFtsAvailable = await loadFTSExtension(undefined, {
363
+ policy: resolveAnalyzeInstallPolicy(),
364
+ });
365
+ if (!repairFtsAvailable) {
366
+ throw new Error('Cannot repair FTS indexes: the LadybugDB FTS extension is unavailable ' +
367
+ '(not pre-installed and could not be installed on this machine). ' +
368
+ 'Run `gitnexus doctor` to install it, then retry `--repair-fts`.');
369
+ }
356
370
  progress('fts', 85, 'Repairing search indexes...');
357
371
  await createSearchFTSIndexes({
358
372
  onIndexStart: options.verbose
@@ -1,32 +1,48 @@
1
- import { createFTSIndex } from '../lbug/lbug-adapter.js';
1
+ import { createFTSIndex, dropFTSIndex } from '../lbug/lbug-adapter.js';
2
2
  import { FTS_INDEXES } from './fts-schema.js';
3
3
  export async function createSearchFTSIndexes(options) {
4
4
  for (const { table, indexName, properties } of FTS_INDEXES) {
5
5
  options?.onIndexStart?.(table, indexName);
6
+ // Drop first so the live `properties` always win. `createFTSIndex` is
7
+ // idempotent-by-name (skips when the index already exists), so without the
8
+ // drop a schema change — e.g. adding `description` (#2299) — would never
9
+ // reach an existing `.lbug` DB on an incremental re-analyze or `--repair-fts`;
10
+ // the old name+content index would silently persist. `dropFTSIndex` no-ops
11
+ // when the index is absent (first-ever analyze) and clears the per-connection
12
+ // memo so the create below actually runs.
13
+ // ponytail: this rebuilds every FTS index on every analyze instead of
14
+ // skipping when present; FTS build is proportional to symbol-table size and
15
+ // runs inside the existing FTS phase. Gate on a stored schema fingerprint if
16
+ // this rebuild cost ever shows up in analyze profiles.
17
+ await dropFTSIndex(table, indexName);
6
18
  await createFTSIndex(table, indexName, [...properties]);
7
19
  options?.onIndexReady?.(table, indexName);
8
20
  }
9
21
  }
10
22
  export async function verifySearchFTSIndexes(executeQuery) {
11
- const safeIdentifier = (value) => {
12
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
13
- throw new Error(`Invalid FTS identifier: ${value}`);
14
- }
15
- return value;
16
- };
23
+ // Read the catalog once and check each configured index both EXISTS and
24
+ // covers its expected columns. A queryability-only probe (CALL QUERY_FTS_INDEX
25
+ // ... catch) is not enough: a stale `name+content`-only index left on a
26
+ // pre-#2299 DB stays queryable yet silently misses `description`, so the probe
27
+ // would pass while doc-comment search is still broken (#2299). SHOW_INDEXES
28
+ // exposes `property_names` (STRING[]) per index, so we assert coverage directly.
29
+ const rows = await executeQuery('CALL SHOW_INDEXES() RETURN *');
30
+ const propsByIndex = new Map();
31
+ for (const row of rows) {
32
+ if (typeof row !== 'object' || row === null)
33
+ continue;
34
+ const record = row;
35
+ const indexName = record.index_name;
36
+ const propertyNames = record.property_names;
37
+ if (typeof indexName !== 'string' || !Array.isArray(propertyNames))
38
+ continue;
39
+ propsByIndex.set(indexName, propertyNames.filter((p) => typeof p === 'string'));
40
+ }
17
41
  const missing = [];
18
- for (const { table, indexName } of FTS_INDEXES) {
19
- const safeTable = safeIdentifier(table);
20
- const safeIndex = safeIdentifier(indexName);
21
- const probe = `
22
- CALL QUERY_FTS_INDEX('${safeTable}', '${safeIndex}', '__gitnexus_fts_probe__', conjunctive := false)
23
- RETURN score
24
- LIMIT 1
25
- `;
26
- try {
27
- await executeQuery(probe);
28
- }
29
- catch {
42
+ for (const { table, indexName, properties } of FTS_INDEXES) {
43
+ const actual = propsByIndex.get(indexName);
44
+ // Absent from the catalog, or present but not covering every expected column.
45
+ if (!actual || !properties.every((p) => actual.includes(p))) {
30
46
  missing.push(`${table}.${indexName}`);
31
47
  }
32
48
  }
@@ -1,7 +1,40 @@
1
+ // Shared by both index creation (`createSearchFTSIndexes`) and querying
2
+ // (`searchFTSFromLbug` / `verifySearchFTSIndexes`) — the single source of truth
3
+ // for which tables/columns are full-text searchable. Adding `description` here
4
+ // makes doc comments (Javadoc/KDoc/JSDoc/Doxygen/godoc/RDoc) keyword-searchable
5
+ // once they are populated by `descriptionExtractor` (#2270/#2286, issue #2299).
6
+ //
7
+ // IMPORTANT: every property must be a real column on its table (see
8
+ // `core/lbug/schema.ts`). `File` has no `description` column, so it stays
9
+ // name+content. All other entries below carry a `description` column.
10
+ //
11
+ // Tables beyond the original 5 mirror `EMBEDDABLE_LABELS` (embeddings/types.ts):
12
+ // indexing the same set keeps a symbol's doc comment both keyword- and
13
+ // semantically-searchable.
14
+ const FTS_PROPERTIES = ['name', 'content', 'description'];
1
15
  export const FTS_INDEXES = [
16
+ // File has no `description` column — keep it name+content only.
2
17
  { table: 'File', indexName: 'file_fts', properties: ['name', 'content'] },
3
- { table: 'Function', indexName: 'function_fts', properties: ['name', 'content'] },
4
- { table: 'Class', indexName: 'class_fts', properties: ['name', 'content'] },
5
- { table: 'Method', indexName: 'method_fts', properties: ['name', 'content'] },
6
- { table: 'Interface', indexName: 'interface_fts', properties: ['name', 'content'] },
18
+ // Original 5 (minus File) gain `description`.
19
+ { table: 'Function', indexName: 'function_fts', properties: FTS_PROPERTIES },
20
+ { table: 'Class', indexName: 'class_fts', properties: FTS_PROPERTIES },
21
+ { table: 'Method', indexName: 'method_fts', properties: FTS_PROPERTIES },
22
+ { table: 'Interface', indexName: 'interface_fts', properties: FTS_PROPERTIES },
23
+ // Remaining EMBEDDABLE_LABELS symbol tables — all CODE_ELEMENT_BASE-shaped
24
+ // (or a superset), so all carry name + content + description columns.
25
+ { table: 'Constructor', indexName: 'constructor_fts', properties: FTS_PROPERTIES },
26
+ { table: 'Struct', indexName: 'struct_fts', properties: FTS_PROPERTIES },
27
+ { table: 'Enum', indexName: 'enum_fts', properties: FTS_PROPERTIES },
28
+ { table: 'Trait', indexName: 'trait_fts', properties: FTS_PROPERTIES },
29
+ { table: 'Impl', indexName: 'impl_fts', properties: FTS_PROPERTIES },
30
+ { table: 'Macro', indexName: 'macro_fts', properties: FTS_PROPERTIES },
31
+ { table: 'Namespace', indexName: 'namespace_fts', properties: FTS_PROPERTIES },
32
+ { table: 'TypeAlias', indexName: 'type_alias_fts', properties: FTS_PROPERTIES },
33
+ { table: 'Typedef', indexName: 'typedef_fts', properties: FTS_PROPERTIES },
34
+ { table: 'Const', indexName: 'const_fts', properties: FTS_PROPERTIES },
35
+ { table: 'Property', indexName: 'property_fts', properties: FTS_PROPERTIES },
36
+ { table: 'Record', indexName: 'record_fts', properties: FTS_PROPERTIES },
37
+ { table: 'Union', indexName: 'union_fts', properties: FTS_PROPERTIES },
38
+ { table: 'Static', indexName: 'static_fts', properties: FTS_PROPERTIES },
39
+ { table: 'Variable', indexName: 'variable_fts', properties: FTS_PROPERTIES },
7
40
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.9-rc.21",
3
+ "version": "1.6.9-rc.22",
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",
@@ -70,6 +70,7 @@ const LBUG_NATIVE = [
70
70
  'test/integration/local-backend-calltool.test.ts',
71
71
  'test/integration/search-core.test.ts',
72
72
  'test/integration/search-pool.test.ts',
73
+ 'test/integration/fts-description-search.test.ts',
73
74
  'test/integration/staleness-and-stability.test.ts',
74
75
  'test/integration/analyze-wal-checkpoint-failure.test.ts',
75
76
  ];