@codragraph/cli 2.1.4 → 2.1.6

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.
Files changed (109) hide show
  1. package/README.md +36 -7
  2. package/dist/cli/ai-context.js +297 -0
  3. package/dist/cli/analyze.d.ts +9 -4
  4. package/dist/cli/analyze.js +37 -13
  5. package/dist/cli/index.js +40 -14
  6. package/dist/cli/status.d.ts +1 -1
  7. package/dist/cli/status.js +8 -0
  8. package/dist/cli/tool.d.ts +10 -2
  9. package/dist/cli/tool.js +100 -39
  10. package/dist/config/ignore-service.js +1 -0
  11. package/dist/core/adaptive-profile.d.ts +52 -0
  12. package/dist/core/adaptive-profile.js +180 -0
  13. package/dist/core/cgdb/cgdb-adapter.d.ts +34 -5
  14. package/dist/core/cgdb/cgdb-adapter.js +418 -5
  15. package/dist/core/cgdb/pool-adapter.js +130 -20
  16. package/dist/core/ingestion/parsing-processor.js +7 -1
  17. package/dist/core/ingestion/pipeline-phases/parse-impl.js +7 -1
  18. package/dist/core/ingestion/pipeline-phases/structure.js +19 -3
  19. package/dist/core/ingestion/pipeline.d.ts +10 -0
  20. package/dist/core/ingestion/workers/parse-worker.js +1 -1
  21. package/dist/core/ingestion/workers/worker-pool.d.ts +14 -1
  22. package/dist/core/ingestion/workers/worker-pool.js +33 -17
  23. package/dist/core/run-analyze.d.ts +27 -2
  24. package/dist/core/run-analyze.js +626 -32
  25. package/dist/core/search/bm25-index.d.ts +16 -8
  26. package/dist/core/search/bm25-index.js +72 -110
  27. package/dist/mcp/local/local-backend.d.ts +2 -0
  28. package/dist/mcp/local/local-backend.js +241 -21
  29. package/dist/storage/repo-manager.d.ts +29 -0
  30. package/dist/web/assets/__vite-browser-external-BIHI7g3E.js +1 -0
  31. package/dist/web/assets/agent-DcdaQnmu.js +1104 -0
  32. package/dist/web/assets/architectureDiagram-UL44E2DR-DFSpa3Hb.js +36 -0
  33. package/dist/web/assets/blockDiagram-7IZFK4PR-DlFaxH1b.js +132 -0
  34. package/dist/web/assets/{c4Diagram-DFAF54RM-C4Hl3J2U.js → c4Diagram-Y2BXMSZH-BjJ_Yrim.js} +1 -1
  35. package/dist/web/assets/{chunk-7RZVMHOQ-BitYcNVR.js → chunk-3SSMPTDK-KGZSzG3Y.js} +1 -1
  36. package/dist/web/assets/{chunk-TBF5ZNIQ-DL5stGM1.js → chunk-6764PJDD-p1sGJgVm.js} +1 -1
  37. package/dist/web/assets/{chunk-KSICW3F5-BYzvDLNI.js → chunk-AZZRMDJM-DIDkQA4V.js} +1 -1
  38. package/dist/web/assets/{chunk-AEOMTBSW-BgTIXPsY.js → chunk-JQRUD6KW-DAwg-yCU.js} +1 -1
  39. package/dist/web/assets/chunk-KRXBNO2N-ChVO_XdS.js +1 -0
  40. package/dist/web/assets/chunk-LCXTWHL2-DGYdb_Eh.js +231 -0
  41. package/dist/web/assets/{chunk-O5ABG6QK-dHwHzA6n.js → chunk-LII3EMHJ-Bzh9SNgD.js} +1 -1
  42. package/dist/web/assets/chunk-RG4AUYOV-Bcl7U_IV.js +206 -0
  43. package/dist/web/assets/{chunk-TU3PZOEN-RLyvLcv-.js → chunk-T5OCTHI4-CZYMg5sc.js} +1 -1
  44. package/dist/web/assets/chunk-W44A43WB-REOI67PN.js +13 -0
  45. package/dist/web/assets/{chunk-RWUO3TPN-BgRTY0_k.js → chunk-ZXARS5L4-BfFdV1tf.js} +1 -1
  46. package/dist/web/assets/classDiagram-KGZ6W3CR-B-qkKMYi.js +1 -0
  47. package/dist/web/assets/classDiagram-v2-72OJOZXJ-B-qkKMYi.js +1 -0
  48. package/dist/web/assets/{cose-bilkent-PNC4W37J-DVhePRYg.js → cose-bilkent-UX7MHV2Q-D6vANJGG.js} +1 -1
  49. package/dist/web/assets/dagre-ND4H6XIP-BiHe5Lal.js +4 -0
  50. package/dist/web/assets/diagram-3NCE3AQN-CEutBCOW.js +43 -0
  51. package/dist/web/assets/diagram-GF46GFSD-CZns6HPQ.js +24 -0
  52. package/dist/web/assets/diagram-HNR7UZ2L-Vz8fE5of.js +3 -0
  53. package/dist/web/assets/diagram-QXG6HAR7-D60HKZ_y.js +24 -0
  54. package/dist/web/assets/diagram-WEQXMOUZ-vGAf1p3E.js +10 -0
  55. package/dist/web/assets/{erDiagram-GCSMX5X6-C3dhDFA8.js → erDiagram-L5TCEMPS-DZaplJA6.js} +5 -5
  56. package/dist/web/assets/{flowDiagram-OTCZ4VVT-CWSFWmhr.js → flowDiagram-H6V6AXG4-BqUqeAsI.js} +9 -9
  57. package/dist/web/assets/ganttDiagram-JCBTUEKG-XEB6H-0G.js +292 -0
  58. package/dist/web/assets/gitGraphDiagram-S2ZK5IYY-7G50u1Cd.js +106 -0
  59. package/dist/web/assets/index-B5WxtMpv.js +1415 -0
  60. package/dist/web/assets/infoDiagram-3YFTVSEB-Cut_rzaf.js +2 -0
  61. package/dist/web/assets/{ishikawaDiagram-YMYX4NHK-DUoJvNP2.js → ishikawaDiagram-BNXS4ZKH-B4DGfGi3.js} +3 -3
  62. package/dist/web/assets/{journeyDiagram-SO5T7YLQ-RMFPNNqz.js → journeyDiagram-M6C3CM3L-BBFhsL3E.js} +1 -1
  63. package/dist/web/assets/{kanban-definition-LJHFXRCJ-BzpDs1K9.js → kanban-definition-75IXJCU3-DarGRyn3.js} +4 -4
  64. package/dist/web/assets/{katex-GD7MH7QM-DBQvrix-.js → katex-K3KEBU37-W5XTYMhr.js} +1 -1
  65. package/dist/web/assets/mindmap-definition-2TDM6QVE-BgeczIJM.js +96 -0
  66. package/dist/web/assets/pieDiagram-CU6KROY3-Kkoo-Noq.js +30 -0
  67. package/dist/web/assets/quadrantDiagram-VICAPDV7-CDQFeRWN.js +7 -0
  68. package/dist/web/assets/{requirementDiagram-M5DCFWZL-DLHOVTSv.js → requirementDiagram-JXO7QTGE-Cz9-XnkA.js} +2 -2
  69. package/dist/web/assets/sankeyDiagram-URQDO5SZ-CU26z0n7.js +40 -0
  70. package/dist/web/assets/sequenceDiagram-VS2MUI6T-OGK1FLOt.js +162 -0
  71. package/dist/web/assets/stateDiagram-7D4R322I-DJ9brq0U.js +1 -0
  72. package/dist/web/assets/stateDiagram-v2-36443NZ5-DhJ4Ky-7.js +1 -0
  73. package/dist/web/assets/{timeline-definition-5SPVSISX-TRSDRgPw.js → timeline-definition-O6YCAMPW-XZvnjqTT.js} +4 -4
  74. package/dist/web/assets/{vennDiagram-IE5QUKF5-DNy7HRBM.js → vennDiagram-MWXL3ELB-CJUssEjA.js} +6 -6
  75. package/dist/web/assets/wardley-L42UT6IY-5TKZOOLJ-DZr11zBG.js +173 -0
  76. package/dist/web/assets/wardleyDiagram-CUQ6CDDI-C276iqrN.js +78 -0
  77. package/dist/web/assets/{xychartDiagram-ZHJ5623Y-Dr9r7a35.js → xychartDiagram-N2JHSOCM-B9-uCZyP.js} +4 -4
  78. package/dist/web/index.html +1 -1
  79. package/hooks/claude/codragraph-hook.cjs +15 -122
  80. package/package.json +1 -1
  81. package/vendor/node_modules/node-addon-api/node_addon_api_except.stamp +0 -0
  82. package/dist/web/assets/agent-D5lb0zXz.js +0 -1089
  83. package/dist/web/assets/architectureDiagram-EMZXCZ2Q-CZtc99v_.js +0 -36
  84. package/dist/web/assets/blockDiagram-IGV67L2C-BtoUp-6Y.js +0 -132
  85. package/dist/web/assets/chunk-3GS5O3IE-DkUjU0WD.js +0 -231
  86. package/dist/web/assets/chunk-3YCYZ6SJ-CQkVgT_z.js +0 -1
  87. package/dist/web/assets/chunk-H3VCZNTA-Cx5XV_aC.js +0 -13
  88. package/dist/web/assets/chunk-HN6EAY2L-BBnyTNdB.js +0 -1
  89. package/dist/web/assets/chunk-PK6DOVAG-CvsEnugt.js +0 -206
  90. package/dist/web/assets/classDiagram-PPOCWD7C-DTr8QIOf.js +0 -1
  91. package/dist/web/assets/classDiagram-v2-23LJLIIU-DTr8QIOf.js +0 -1
  92. package/dist/web/assets/dagre-E77IOHMT-Dzx0A6ZU.js +0 -4
  93. package/dist/web/assets/diagram-H7BISOXX-CC9pRew1.js +0 -43
  94. package/dist/web/assets/diagram-JC5VWROH-Bau_i9tf.js +0 -24
  95. package/dist/web/assets/diagram-LXUTUG65-D9_FM2Gt.js +0 -10
  96. package/dist/web/assets/diagram-WEHSV5V5-BMlayouL.js +0 -24
  97. package/dist/web/assets/ganttDiagram-MUNLMDZQ-D3a67Yol.js +0 -292
  98. package/dist/web/assets/gitGraphDiagram-3HKGZ4G3-7jmry-vM.js +0 -106
  99. package/dist/web/assets/index-BgeqpYgd.js +0 -1415
  100. package/dist/web/assets/infoDiagram-MN7RKWGX-G7lhP0Ib.js +0 -2
  101. package/dist/web/assets/mindmap-definition-2EUWGEK5-Bk0O4roa.js +0 -96
  102. package/dist/web/assets/pieDiagram-3IATQBI2-DKU7kpgS.js +0 -30
  103. package/dist/web/assets/quadrantDiagram-E256RVCF-BY0TGWCS.js +0 -7
  104. package/dist/web/assets/sankeyDiagram-L3NBLAOT-DVMj5rX2.js +0 -10
  105. package/dist/web/assets/sequenceDiagram-ZOUHS735-CJC73bV-.js +0 -157
  106. package/dist/web/assets/stateDiagram-MLPALWAM-BCFyESls.js +0 -1
  107. package/dist/web/assets/stateDiagram-v2-B5LQ5ZB2-DahzzIca.js +0 -1
  108. package/dist/web/assets/wardley-RL74JXVD-BCRCBASE-B-eZEzf9.js +0 -161
  109. package/dist/web/assets/wardleyDiagram-XU3VSMPF-BP-r1xzR.js +0 -20
@@ -15,18 +15,26 @@ export interface BM25SearchResult {
15
15
  score: number;
16
16
  rank: number;
17
17
  nodeIds?: string[];
18
+ /** False when results came from the bounded fallback scan instead of FTS. */
19
+ ftsUsed?: boolean;
18
20
  }
19
21
  /**
20
- * Drop all ensured-FTS cache entries for a given repoId.
22
+ * FTS table set served by `searchFTSFromCgdb`. Centralised so that both
23
+ * the CLI/pipeline path and the MCP pool path stay in lockstep.
21
24
  *
22
- * Called from the pool-close listener so that a pool teardown / recreation
23
- * forces the next `searchFTSFromCgdb` 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.
25
+ * The properties list is computed at FTS-create time via `ftsPropertiesFor`
26
+ * for repos that were analysed with `--compress brotli|zstd`, the
27
+ * `content` column holds base64-of-encoded-bytes and would tokenise to
28
+ * useless tokens. Those repos get name-only FTS so search at least
29
+ * matches function/class names instead of returning random hits on
30
+ * base64 alphabet. Plain (compress='none' / unset) repos get the full
31
+ * `name + content` index for body-text matches. RFC 0001 Phase 2.5.
28
32
  */
29
- export declare function invalidateEnsuredFTSForRepo(repoId: string): void;
33
+ export declare const FTS_TABLES: ReadonlyArray<{
34
+ table: string;
35
+ indexName: string;
36
+ }>;
37
+ export declare const ftsPropertiesFor: (compress: "none" | "brotli" | "zstd" | undefined) => readonly string[];
30
38
  /**
31
39
  * Search using LadybugDB's built-in FTS (always fresh, reads from disk)
32
40
  *
@@ -23,22 +23,21 @@ import { queryFTS, ensureFTSIndex, executeQuery as executeCoreQuery, } from '../
23
23
  * base64 alphabet. Plain (compress='none' / unset) repos get the full
24
24
  * `name + content` index for body-text matches. RFC 0001 Phase 2.5.
25
25
  */
26
- const FTS_TABLES = [
26
+ export const FTS_TABLES = [
27
27
  { table: 'File', indexName: 'file_fts' },
28
28
  { table: 'Function', indexName: 'function_fts' },
29
29
  { table: 'Class', indexName: 'class_fts' },
30
30
  { table: 'Method', indexName: 'method_fts' },
31
31
  { table: 'Interface', indexName: 'interface_fts' },
32
32
  ];
33
- const ftsPropertiesFor = (compress) => !compress || compress === 'none' ? ['name', 'content'] : ['name'];
33
+ export const ftsPropertiesFor = (compress) => (!compress || compress === 'none' ? ['name', 'content'] : ['name']);
34
34
  /**
35
- * Look up `meta.compress` for a repo. The MCP path passes `repoId`
36
- * (registry-derived); the CLI path passes nothing and we walk up from
37
- * cwd. Returns `'none'` whenever the lookup fails so the safe default
38
- * (full FTS index) is used the failure mode is reduced search
39
- * quality, never wrong results.
35
+ * Look up query-related index metadata for a repo. The MCP path passes `repoId`
36
+ * (registry-derived); the CLI/core path passes nothing and we walk up from
37
+ * cwd. Read-only callers use `ftsReady` to avoid expensive probes for
38
+ * FTS indexes that older analyses never wrote.
40
39
  */
41
- async function getCompressMode(repoId) {
40
+ async function getSearchMeta(repoId) {
42
41
  try {
43
42
  const repoMod = await import('../../storage/repo-manager.js');
44
43
  if (repoId) {
@@ -50,16 +49,22 @@ async function getCompressMode(repoId) {
50
49
  const base = entry.name.toLowerCase();
51
50
  if (base === repoId || repoId.startsWith(`${base}-`)) {
52
51
  const meta = await repoMod.loadMeta(entry.storagePath);
53
- return meta?.compress ?? 'none';
52
+ return {
53
+ compress: meta?.compress ?? 'none',
54
+ ftsReady: meta?.searchIndexes?.fts === true,
55
+ };
54
56
  }
55
57
  }
56
- return 'none';
58
+ return { compress: 'none', ftsReady: false };
57
59
  }
58
60
  const repo = await repoMod.findRepo(process.cwd());
59
- return repo?.meta?.compress ?? 'none';
61
+ return {
62
+ compress: repo?.meta?.compress ?? 'none',
63
+ ftsReady: repo?.meta?.searchIndexes?.fts,
64
+ };
60
65
  }
61
66
  catch {
62
- return 'none';
67
+ return { compress: 'none', ftsReady: repoId ? false : undefined };
63
68
  }
64
69
  }
65
70
  const FALLBACK_SCAN_LIMIT = 50_000;
@@ -69,76 +74,6 @@ const FALLBACK_FIELD_WEIGHTS = {
69
74
  content: 2,
70
75
  description: 1,
71
76
  };
72
- /**
73
- * Per-process cache for the MCP pool path: tracks which `(repoId, table)`
74
- * pairs have been ensured. The CLI/pipeline path gets its own cache inside
75
- * `cgdb-adapter.ts` keyed by table/index, scoped to the singleton connection.
76
- *
77
- * IMPORTANT: an entry is added ONLY when the index was confirmed to exist
78
- * (CREATE_FTS_INDEX succeeded, or failed with `'already exists'`). Other
79
- * failures (transient lock errors, missing extension, etc.) leave the key
80
- * unset so the next query retries instead of silently caching the failure.
81
- *
82
- * Entries for a given repoId are invalidated when its pool is closed —
83
- * see the `addPoolCloseListener` registration in `searchFTSFromCgdb`.
84
- */
85
- const ensuredPoolFTS = new Set();
86
- /**
87
- * Drop all ensured-FTS cache entries for a given repoId.
88
- *
89
- * Called from the pool-close listener so that a pool teardown / recreation
90
- * forces the next `searchFTSFromCgdb` call to re-issue `CREATE_FTS_INDEX`
91
- * against the fresh connection rather than trust stale ensure-state from a
92
- * previous pool lifetime.
93
- *
94
- * Exported for tests; the listener wiring is internal.
95
- */
96
- export function invalidateEnsuredFTSForRepo(repoId) {
97
- const prefix = `${repoId}:`;
98
- for (const key of ensuredPoolFTS) {
99
- if (key.startsWith(prefix))
100
- ensuredPoolFTS.delete(key);
101
- }
102
- }
103
- /**
104
- * Tracks whether we've already wired the pool-close listener for this
105
- * process. The pool adapter is dynamically imported, so registration
106
- * happens lazily on the first MCP-pool-backed FTS query.
107
- */
108
- let poolCloseListenerRegistered = false;
109
- function registerPoolCloseListenerOnce(addPoolCloseListener) {
110
- if (poolCloseListenerRegistered)
111
- return;
112
- poolCloseListenerRegistered = true;
113
- addPoolCloseListener((repoId) => invalidateEnsuredFTSForRepo(repoId));
114
- }
115
- async function ensureFTSIndexViaExecutor(executor, repoId, table, indexName, properties) {
116
- const key = `${repoId}:${table}:${indexName}`;
117
- if (ensuredPoolFTS.has(key))
118
- return;
119
- const propList = properties.map((p) => `'${p}'`).join(', ');
120
- try {
121
- await executor(`CALL CREATE_FTS_INDEX('${table}', '${indexName}', [${propList}], stemmer := 'porter')`);
122
- // Index was created successfully — safe to cache.
123
- ensuredPoolFTS.add(key);
124
- }
125
- catch (e) {
126
- // 'already exists' is the happy path (index persists on disk between
127
- // process invocations) — cache it. Anything else is treated as a
128
- // transient failure: surface a one-time warning and leave the key
129
- // unset so the NEXT query retries rather than silently using a
130
- // cached failure (which previously disabled BM25 for the whole
131
- // process for that repo).
132
- const msg = String(e?.message ?? '');
133
- if (msg.includes('already exists')) {
134
- ensuredPoolFTS.add(key);
135
- }
136
- else {
137
- console.warn(`[codragraph] FTS index ensure failed for repo "${repoId}" table "${table}" ` +
138
- `(index "${indexName}"): ${msg || e}. Will retry on next query.`);
139
- }
140
- }
141
- }
142
77
  /**
143
78
  * Execute a single FTS query via a custom executor (for MCP connection pool).
144
79
  * Returns the same shape as core queryFTS (from LadybugDB adapter).
@@ -175,6 +110,22 @@ function searchTerms(query) {
175
110
  ?.filter((term) => term.length > 1 && !BOOLEAN_QUERY_TOKENS.has(term));
176
111
  return [...new Set(terms ?? [])];
177
112
  }
113
+ function searchTermVariants(query) {
114
+ const rawTerms = query.match(/[\p{L}\p{N}_]+/gu) ?? [];
115
+ const variants = new Set();
116
+ for (const raw of rawTerms) {
117
+ const lower = raw.toLowerCase();
118
+ if (raw.length <= 1 || BOOLEAN_QUERY_TOKENS.has(lower))
119
+ continue;
120
+ variants.add(raw);
121
+ variants.add(lower);
122
+ variants.add(raw.charAt(0).toUpperCase() + raw.slice(1).toLowerCase());
123
+ }
124
+ return [...variants];
125
+ }
126
+ function cypherString(value) {
127
+ return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "''")}'`;
128
+ }
178
129
  function scoreFallbackNode(node, query, properties) {
179
130
  const terms = searchTerms(query);
180
131
  if (terms.length === 0)
@@ -201,10 +152,15 @@ function scoreFallbackNode(node, query, properties) {
201
152
  }
202
153
  async function queryFallbackViaExecutor(executor, tableName, properties, query, limit) {
203
154
  try {
155
+ const variants = searchTermVariants(query);
156
+ const conditions = variants.flatMap((term) => properties.map((property) => `node.${property} CONTAINS ${cypherString(term)}`));
157
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' OR ')}` : '';
158
+ const candidateLimit = Math.min(FALLBACK_SCAN_LIMIT, Math.max(limit * 25, 200));
204
159
  const rows = await executor(`
205
160
  MATCH (node:${tableName})
161
+ ${whereClause}
206
162
  RETURN node
207
- LIMIT ${FALLBACK_SCAN_LIMIT}
163
+ LIMIT ${candidateLimit}
208
164
  `);
209
165
  return rows
210
166
  .map((row) => {
@@ -228,10 +184,7 @@ async function fallbackSearchAllTables(executor, query, limit,
228
184
  // pre-Phase-2 behaviour (`['name', 'content']`) for callers that don't
229
185
  // pass a value.
230
186
  properties = ['name', 'content']) {
231
- const results = [];
232
- for (const { table } of FTS_TABLES) {
233
- results.push(await queryFallbackViaExecutor(executor, table, properties, query, limit));
234
- }
187
+ const results = await Promise.all(FTS_TABLES.map(({ table }) => queryFallbackViaExecutor(executor, table, properties, query, limit)));
235
188
  return results;
236
189
  }
237
190
  /**
@@ -249,50 +202,56 @@ export const searchFTSFromCgdb = async (query, limit = 20, repoId) => {
249
202
  if (!query.trim() || limit <= 0)
250
203
  return [];
251
204
  let fileResults, functionResults, classResults, methodResults, interfaceResults;
205
+ let usedFallback = false;
252
206
  if (repoId) {
253
207
  // Use MCP connection pool via dynamic import
254
208
  // IMPORTANT: FTS queries run sequentially to avoid connection contention.
255
209
  // The MCP pool supports multiple connections, but FTS is best run serially.
256
210
  const poolMod = await import('../cgdb/pool-adapter.js');
257
- const { executeQuery, addPoolCloseListener } = poolMod;
258
- // Register the pool-close listener lazily on first use so a teardown of
259
- // the pool entry (LRU eviction, idle timeout, explicit close) drops the
260
- // matching `ensuredPoolFTS` entries. Without this, stale ensure-state
261
- // can outlive the pool that produced it.
262
- registerPoolCloseListenerOnce(addPoolCloseListener);
211
+ const { executeQuery } = poolMod;
263
212
  const executor = (cypher) => executeQuery(repoId, cypher);
264
- // Lazy-create FTS indexes on first query for this repo (analyze no longer
265
- // creates them up-front, so we ensure them here). Cached per-process.
266
- // RFC 0001 Phase 2.5: drop `content` from FTS properties for repos
267
- // analysed with --compress brotli|zstd the column holds encoded
268
- // bytes and would tokenise to garbage.
269
- const compress = await getCompressMode(repoId);
270
- const properties = ftsPropertiesFor(compress);
271
- for (const { table, indexName } of FTS_TABLES) {
272
- await ensureFTSIndexViaExecutor(executor, repoId, table, indexName, properties);
213
+ // The MCP/LocalBackend pool is opened read-only so it can safely coexist
214
+ // with `codragraph analyze`. Do not issue CREATE_FTS_INDEX here: when
215
+ // persisted FTS indexes are missing, meta.searchIndexes lets us skip
216
+ // QUERY_FTS_INDEX and go straight to the bounded fallback below.
217
+ // RFC 0001 Phase 2.5: drop `content` from fallback scoring for repos
218
+ // analysed with --compress brotli|zstd the column holds encoded bytes.
219
+ const meta = await getSearchMeta(repoId);
220
+ const properties = ftsPropertiesFor(meta.compress);
221
+ if (meta.ftsReady) {
222
+ fileResults = await queryFTSViaExecutor(executor, 'File', 'file_fts', query, limit);
223
+ functionResults = await queryFTSViaExecutor(executor, 'Function', 'function_fts', query, limit);
224
+ classResults = await queryFTSViaExecutor(executor, 'Class', 'class_fts', query, limit);
225
+ methodResults = await queryFTSViaExecutor(executor, 'Method', 'method_fts', query, limit);
226
+ interfaceResults = await queryFTSViaExecutor(executor, 'Interface', 'interface_fts', query, limit);
227
+ }
228
+ else {
229
+ usedFallback = true;
230
+ fileResults = [];
231
+ functionResults = [];
232
+ classResults = [];
233
+ methodResults = [];
234
+ interfaceResults = [];
273
235
  }
274
- fileResults = await queryFTSViaExecutor(executor, 'File', 'file_fts', query, limit);
275
- functionResults = await queryFTSViaExecutor(executor, 'Function', 'function_fts', query, limit);
276
- classResults = await queryFTSViaExecutor(executor, 'Class', 'class_fts', query, limit);
277
- methodResults = await queryFTSViaExecutor(executor, 'Method', 'method_fts', query, limit);
278
- interfaceResults = await queryFTSViaExecutor(executor, 'Interface', 'interface_fts', query, limit);
279
236
  if (fileResults.length +
280
237
  functionResults.length +
281
238
  classResults.length +
282
239
  methodResults.length +
283
240
  interfaceResults.length ===
284
241
  0) {
242
+ usedFallback = true;
285
243
  [fileResults, functionResults, classResults, methodResults, interfaceResults] =
286
244
  await fallbackSearchAllTables(executor, query, limit, properties);
287
245
  }
288
246
  }
289
247
  else {
290
248
  // Use core cgdb adapter (CLI / pipeline context) — also sequential for safety.
291
- // Lazy-create FTS indexes on first query (analyze no longer does it).
249
+ // Defensive fallback for older indexes or direct core callers: create FTS
250
+ // indexes on first query when this process owns a writable connection.
292
251
  // RFC 0001 Phase 2.5 — same `compress`-aware property selection as the MCP
293
252
  // path; the CLI walks up from cwd to find the repo's meta.json.
294
- const compress = await getCompressMode();
295
- const properties = ftsPropertiesFor(compress);
253
+ const meta = await getSearchMeta();
254
+ const properties = ftsPropertiesFor(meta.compress);
296
255
  for (const { table, indexName } of FTS_TABLES) {
297
256
  await ensureFTSIndex(table, indexName, [...properties]).catch(() => { });
298
257
  }
@@ -307,6 +266,7 @@ export const searchFTSFromCgdb = async (query, limit = 20, repoId) => {
307
266
  methodResults.length +
308
267
  interfaceResults.length ===
309
268
  0) {
269
+ usedFallback = true;
310
270
  [fileResults, functionResults, classResults, methodResults, interfaceResults] =
311
271
  await fallbackSearchAllTables(executeCoreQuery, query, limit, properties);
312
272
  }
@@ -335,6 +295,7 @@ export const searchFTSFromCgdb = async (query, limit = 20, repoId) => {
335
295
  filePath,
336
296
  score: top3.reduce((acc, e) => acc + e.score, 0),
337
297
  nodeIds: top3.map((e) => e.nodeId).filter((id) => id),
298
+ ftsUsed: !usedFallback,
338
299
  });
339
300
  }
340
301
  // Sort by score descending and add rank
@@ -346,5 +307,6 @@ export const searchFTSFromCgdb = async (query, limit = 20, repoId) => {
346
307
  score: r.score,
347
308
  rank: index + 1,
348
309
  nodeIds: r.nodeIds,
310
+ ftsUsed: r.ftsUsed,
349
311
  }));
350
312
  };
@@ -186,7 +186,9 @@ export declare class LocalBackend {
186
186
  */
187
187
  private semanticSearch;
188
188
  executeCypher(repoName: string, query: string): Promise<any>;
189
+ private trySimpleGraphstoreNodeScan;
189
190
  private cypher;
191
+ private executeLabellessNodeScan;
190
192
  /**
191
193
  * Format raw Cypher result rows as a markdown table for LLM readability.
192
194
  * Falls back to raw result if rows aren't tabular objects.