@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.
- package/README.md +36 -7
- package/dist/cli/ai-context.js +297 -0
- package/dist/cli/analyze.d.ts +9 -4
- package/dist/cli/analyze.js +37 -13
- package/dist/cli/index.js +40 -14
- package/dist/cli/status.d.ts +1 -1
- package/dist/cli/status.js +8 -0
- package/dist/cli/tool.d.ts +10 -2
- package/dist/cli/tool.js +100 -39
- package/dist/config/ignore-service.js +1 -0
- package/dist/core/adaptive-profile.d.ts +52 -0
- package/dist/core/adaptive-profile.js +180 -0
- package/dist/core/cgdb/cgdb-adapter.d.ts +34 -5
- package/dist/core/cgdb/cgdb-adapter.js +418 -5
- package/dist/core/cgdb/pool-adapter.js +130 -20
- package/dist/core/ingestion/parsing-processor.js +7 -1
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +7 -1
- package/dist/core/ingestion/pipeline-phases/structure.js +19 -3
- package/dist/core/ingestion/pipeline.d.ts +10 -0
- package/dist/core/ingestion/workers/parse-worker.js +1 -1
- package/dist/core/ingestion/workers/worker-pool.d.ts +14 -1
- package/dist/core/ingestion/workers/worker-pool.js +33 -17
- package/dist/core/run-analyze.d.ts +27 -2
- package/dist/core/run-analyze.js +626 -32
- package/dist/core/search/bm25-index.d.ts +16 -8
- package/dist/core/search/bm25-index.js +72 -110
- package/dist/mcp/local/local-backend.d.ts +2 -0
- package/dist/mcp/local/local-backend.js +241 -21
- package/dist/storage/repo-manager.d.ts +29 -0
- package/dist/web/assets/__vite-browser-external-BIHI7g3E.js +1 -0
- package/dist/web/assets/agent-DcdaQnmu.js +1104 -0
- package/dist/web/assets/architectureDiagram-UL44E2DR-DFSpa3Hb.js +36 -0
- package/dist/web/assets/blockDiagram-7IZFK4PR-DlFaxH1b.js +132 -0
- package/dist/web/assets/{c4Diagram-DFAF54RM-C4Hl3J2U.js → c4Diagram-Y2BXMSZH-BjJ_Yrim.js} +1 -1
- package/dist/web/assets/{chunk-7RZVMHOQ-BitYcNVR.js → chunk-3SSMPTDK-KGZSzG3Y.js} +1 -1
- package/dist/web/assets/{chunk-TBF5ZNIQ-DL5stGM1.js → chunk-6764PJDD-p1sGJgVm.js} +1 -1
- package/dist/web/assets/{chunk-KSICW3F5-BYzvDLNI.js → chunk-AZZRMDJM-DIDkQA4V.js} +1 -1
- package/dist/web/assets/{chunk-AEOMTBSW-BgTIXPsY.js → chunk-JQRUD6KW-DAwg-yCU.js} +1 -1
- package/dist/web/assets/chunk-KRXBNO2N-ChVO_XdS.js +1 -0
- package/dist/web/assets/chunk-LCXTWHL2-DGYdb_Eh.js +231 -0
- package/dist/web/assets/{chunk-O5ABG6QK-dHwHzA6n.js → chunk-LII3EMHJ-Bzh9SNgD.js} +1 -1
- package/dist/web/assets/chunk-RG4AUYOV-Bcl7U_IV.js +206 -0
- package/dist/web/assets/{chunk-TU3PZOEN-RLyvLcv-.js → chunk-T5OCTHI4-CZYMg5sc.js} +1 -1
- package/dist/web/assets/chunk-W44A43WB-REOI67PN.js +13 -0
- package/dist/web/assets/{chunk-RWUO3TPN-BgRTY0_k.js → chunk-ZXARS5L4-BfFdV1tf.js} +1 -1
- package/dist/web/assets/classDiagram-KGZ6W3CR-B-qkKMYi.js +1 -0
- package/dist/web/assets/classDiagram-v2-72OJOZXJ-B-qkKMYi.js +1 -0
- package/dist/web/assets/{cose-bilkent-PNC4W37J-DVhePRYg.js → cose-bilkent-UX7MHV2Q-D6vANJGG.js} +1 -1
- package/dist/web/assets/dagre-ND4H6XIP-BiHe5Lal.js +4 -0
- package/dist/web/assets/diagram-3NCE3AQN-CEutBCOW.js +43 -0
- package/dist/web/assets/diagram-GF46GFSD-CZns6HPQ.js +24 -0
- package/dist/web/assets/diagram-HNR7UZ2L-Vz8fE5of.js +3 -0
- package/dist/web/assets/diagram-QXG6HAR7-D60HKZ_y.js +24 -0
- package/dist/web/assets/diagram-WEQXMOUZ-vGAf1p3E.js +10 -0
- package/dist/web/assets/{erDiagram-GCSMX5X6-C3dhDFA8.js → erDiagram-L5TCEMPS-DZaplJA6.js} +5 -5
- package/dist/web/assets/{flowDiagram-OTCZ4VVT-CWSFWmhr.js → flowDiagram-H6V6AXG4-BqUqeAsI.js} +9 -9
- package/dist/web/assets/ganttDiagram-JCBTUEKG-XEB6H-0G.js +292 -0
- package/dist/web/assets/gitGraphDiagram-S2ZK5IYY-7G50u1Cd.js +106 -0
- package/dist/web/assets/index-B5WxtMpv.js +1415 -0
- package/dist/web/assets/infoDiagram-3YFTVSEB-Cut_rzaf.js +2 -0
- package/dist/web/assets/{ishikawaDiagram-YMYX4NHK-DUoJvNP2.js → ishikawaDiagram-BNXS4ZKH-B4DGfGi3.js} +3 -3
- package/dist/web/assets/{journeyDiagram-SO5T7YLQ-RMFPNNqz.js → journeyDiagram-M6C3CM3L-BBFhsL3E.js} +1 -1
- package/dist/web/assets/{kanban-definition-LJHFXRCJ-BzpDs1K9.js → kanban-definition-75IXJCU3-DarGRyn3.js} +4 -4
- package/dist/web/assets/{katex-GD7MH7QM-DBQvrix-.js → katex-K3KEBU37-W5XTYMhr.js} +1 -1
- package/dist/web/assets/mindmap-definition-2TDM6QVE-BgeczIJM.js +96 -0
- package/dist/web/assets/pieDiagram-CU6KROY3-Kkoo-Noq.js +30 -0
- package/dist/web/assets/quadrantDiagram-VICAPDV7-CDQFeRWN.js +7 -0
- package/dist/web/assets/{requirementDiagram-M5DCFWZL-DLHOVTSv.js → requirementDiagram-JXO7QTGE-Cz9-XnkA.js} +2 -2
- package/dist/web/assets/sankeyDiagram-URQDO5SZ-CU26z0n7.js +40 -0
- package/dist/web/assets/sequenceDiagram-VS2MUI6T-OGK1FLOt.js +162 -0
- package/dist/web/assets/stateDiagram-7D4R322I-DJ9brq0U.js +1 -0
- package/dist/web/assets/stateDiagram-v2-36443NZ5-DhJ4Ky-7.js +1 -0
- package/dist/web/assets/{timeline-definition-5SPVSISX-TRSDRgPw.js → timeline-definition-O6YCAMPW-XZvnjqTT.js} +4 -4
- package/dist/web/assets/{vennDiagram-IE5QUKF5-DNy7HRBM.js → vennDiagram-MWXL3ELB-CJUssEjA.js} +6 -6
- package/dist/web/assets/wardley-L42UT6IY-5TKZOOLJ-DZr11zBG.js +173 -0
- package/dist/web/assets/wardleyDiagram-CUQ6CDDI-C276iqrN.js +78 -0
- package/dist/web/assets/{xychartDiagram-ZHJ5623Y-Dr9r7a35.js → xychartDiagram-N2JHSOCM-B9-uCZyP.js} +4 -4
- package/dist/web/index.html +1 -1
- package/hooks/claude/codragraph-hook.cjs +15 -122
- package/package.json +1 -1
- package/vendor/node_modules/node-addon-api/node_addon_api_except.stamp +0 -0
- package/dist/web/assets/agent-D5lb0zXz.js +0 -1089
- package/dist/web/assets/architectureDiagram-EMZXCZ2Q-CZtc99v_.js +0 -36
- package/dist/web/assets/blockDiagram-IGV67L2C-BtoUp-6Y.js +0 -132
- package/dist/web/assets/chunk-3GS5O3IE-DkUjU0WD.js +0 -231
- package/dist/web/assets/chunk-3YCYZ6SJ-CQkVgT_z.js +0 -1
- package/dist/web/assets/chunk-H3VCZNTA-Cx5XV_aC.js +0 -13
- package/dist/web/assets/chunk-HN6EAY2L-BBnyTNdB.js +0 -1
- package/dist/web/assets/chunk-PK6DOVAG-CvsEnugt.js +0 -206
- package/dist/web/assets/classDiagram-PPOCWD7C-DTr8QIOf.js +0 -1
- package/dist/web/assets/classDiagram-v2-23LJLIIU-DTr8QIOf.js +0 -1
- package/dist/web/assets/dagre-E77IOHMT-Dzx0A6ZU.js +0 -4
- package/dist/web/assets/diagram-H7BISOXX-CC9pRew1.js +0 -43
- package/dist/web/assets/diagram-JC5VWROH-Bau_i9tf.js +0 -24
- package/dist/web/assets/diagram-LXUTUG65-D9_FM2Gt.js +0 -10
- package/dist/web/assets/diagram-WEHSV5V5-BMlayouL.js +0 -24
- package/dist/web/assets/ganttDiagram-MUNLMDZQ-D3a67Yol.js +0 -292
- package/dist/web/assets/gitGraphDiagram-3HKGZ4G3-7jmry-vM.js +0 -106
- package/dist/web/assets/index-BgeqpYgd.js +0 -1415
- package/dist/web/assets/infoDiagram-MN7RKWGX-G7lhP0Ib.js +0 -2
- package/dist/web/assets/mindmap-definition-2EUWGEK5-Bk0O4roa.js +0 -96
- package/dist/web/assets/pieDiagram-3IATQBI2-DKU7kpgS.js +0 -30
- package/dist/web/assets/quadrantDiagram-E256RVCF-BY0TGWCS.js +0 -7
- package/dist/web/assets/sankeyDiagram-L3NBLAOT-DVMj5rX2.js +0 -10
- package/dist/web/assets/sequenceDiagram-ZOUHS735-CJC73bV-.js +0 -157
- package/dist/web/assets/stateDiagram-MLPALWAM-BCFyESls.js +0 -1
- package/dist/web/assets/stateDiagram-v2-B5LQ5ZB2-DahzzIca.js +0 -1
- package/dist/web/assets/wardley-RL74JXVD-BCRCBASE-B-eZEzf9.js +0 -161
- 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
|
-
*
|
|
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
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
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
|
|
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
|
|
36
|
-
* (registry-derived); the CLI path passes nothing and we walk up from
|
|
37
|
-
* cwd.
|
|
38
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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 ${
|
|
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
|
|
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
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
//
|
|
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
|
|
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.
|