@codragraph/cli 2.0.0 → 2.1.0
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 +2 -2
- package/dist/_shared/cgdb/schema-constants.d.ts +16 -0
- package/dist/_shared/cgdb/schema-constants.d.ts.map +1 -0
- package/dist/_shared/cgdb/schema-constants.js +67 -0
- package/dist/_shared/cgdb/schema-constants.js.map +1 -0
- package/dist/_shared/index.d.ts +2 -2
- package/dist/_shared/index.js +1 -1
- package/dist/cli/analyze.js +3 -3
- package/dist/cli/graphstore.js +21 -21
- package/dist/cli/index-repo.js +3 -3
- package/dist/cli/wiki.js +3 -3
- package/dist/core/augmentation/engine.js +7 -7
- package/dist/core/cgdb/cgdb-adapter.d.ts +176 -0
- package/dist/core/cgdb/cgdb-adapter.js +1320 -0
- package/dist/core/cgdb/content-read.d.ts +46 -0
- package/dist/core/cgdb/content-read.js +64 -0
- package/dist/core/cgdb/csv-generator.d.ts +29 -0
- package/dist/core/cgdb/csv-generator.js +492 -0
- package/dist/core/cgdb/pool-adapter.d.ts +93 -0
- package/dist/core/cgdb/pool-adapter.js +550 -0
- package/dist/core/cgdb/schema.d.ts +62 -0
- package/dist/core/cgdb/schema.js +502 -0
- package/dist/core/embeddings/embedding-pipeline.js +4 -4
- package/dist/core/graphstore/cgdb-row-source.d.ts +19 -0
- package/dist/core/graphstore/cgdb-row-source.js +141 -0
- package/dist/core/graphstore/index.d.ts +1 -1
- package/dist/core/graphstore/index.js +3 -3
- package/dist/core/group/bridge-db.d.ts +2 -2
- package/dist/core/group/bridge-db.js +18 -18
- package/dist/core/group/bridge-schema.d.ts +4 -4
- package/dist/core/group/bridge-schema.js +4 -4
- package/dist/core/group/cross-impact.js +3 -3
- package/dist/core/group/sync.js +4 -4
- package/dist/core/run-analyze.js +24 -24
- package/dist/core/search/bm25-index.d.ts +3 -3
- package/dist/core/search/bm25-index.js +9 -9
- package/dist/core/search/hybrid-search.js +2 -2
- package/dist/core/wiki/generator.d.ts +2 -2
- package/dist/core/wiki/generator.js +4 -4
- package/dist/core/wiki/graph-queries.d.ts +2 -2
- package/dist/core/wiki/graph-queries.js +5 -5
- package/dist/mcp/core/cgdb-adapter.d.ts +5 -0
- package/dist/mcp/core/cgdb-adapter.js +5 -0
- package/dist/mcp/core/embedder.js +1 -1
- package/dist/mcp/local/local-backend.d.ts +2 -2
- package/dist/mcp/local/local-backend.js +15 -15
- package/dist/mcp/server.js +3 -3
- package/dist/mcp/tools.js +1 -1
- package/dist/server/analyze-worker.js +2 -2
- package/dist/server/api.js +31 -31
- package/dist/storage/repo-manager.d.ts +4 -4
- package/dist/storage/repo-manager.js +5 -5
- package/hooks/claude/codragraph-hook.cjs +4 -4
- package/package.json +3 -3
- package/scripts/build.js +8 -9
- package/vendor/tree-sitter-proto/bindings/node/index.js +3 -3
- package/vendor/tree-sitter-proto/src/node-types.json +1 -1
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* This is the same approach used by Elasticsearch, Pinecone, and other
|
|
8
8
|
* production search systems.
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
10
|
+
import { searchFTSFromCgdb } from './bm25-index.js';
|
|
11
11
|
/**
|
|
12
12
|
* RRF constant - standard value used in the literature
|
|
13
13
|
* Higher values give more weight to lower-ranked results
|
|
@@ -112,7 +112,7 @@ export const formatHybridResults = (results) => {
|
|
|
112
112
|
*/
|
|
113
113
|
export const hybridSearch = async (query, limit, executeQuery, semanticSearch) => {
|
|
114
114
|
// Use LadybugDB FTS for always-fresh BM25 results
|
|
115
|
-
const bm25Results = await
|
|
115
|
+
const bm25Results = await searchFTSFromCgdb(query, limit);
|
|
116
116
|
const semanticResults = await semanticSearch(executeQuery, query, limit);
|
|
117
117
|
return mergeWithRRF(bm25Results, semanticResults, limit);
|
|
118
118
|
};
|
|
@@ -41,14 +41,14 @@ export declare class WikiGenerator {
|
|
|
41
41
|
private repoPath;
|
|
42
42
|
private storagePath;
|
|
43
43
|
private wikiDir;
|
|
44
|
-
private
|
|
44
|
+
private cgdbPath;
|
|
45
45
|
private llmConfig;
|
|
46
46
|
private maxTokensPerModule;
|
|
47
47
|
private concurrency;
|
|
48
48
|
private options;
|
|
49
49
|
private onProgress;
|
|
50
50
|
private failedModules;
|
|
51
|
-
constructor(repoPath: string, storagePath: string,
|
|
51
|
+
constructor(repoPath: string, storagePath: string, cgdbPath: string, llmConfig: LLMConfig, options?: WikiOptions, onProgress?: ProgressCallback);
|
|
52
52
|
private lastPercent;
|
|
53
53
|
/**
|
|
54
54
|
* Create streaming options that report LLM progress to the progress bar.
|
|
@@ -26,18 +26,18 @@ export class WikiGenerator {
|
|
|
26
26
|
repoPath;
|
|
27
27
|
storagePath;
|
|
28
28
|
wikiDir;
|
|
29
|
-
|
|
29
|
+
cgdbPath;
|
|
30
30
|
llmConfig;
|
|
31
31
|
maxTokensPerModule;
|
|
32
32
|
concurrency;
|
|
33
33
|
options;
|
|
34
34
|
onProgress;
|
|
35
35
|
failedModules = [];
|
|
36
|
-
constructor(repoPath, storagePath,
|
|
36
|
+
constructor(repoPath, storagePath, cgdbPath, llmConfig, options = {}, onProgress) {
|
|
37
37
|
this.repoPath = repoPath;
|
|
38
38
|
this.storagePath = storagePath;
|
|
39
39
|
this.wikiDir = path.join(storagePath, WIKI_DIR);
|
|
40
|
-
this.
|
|
40
|
+
this.cgdbPath = cgdbPath;
|
|
41
41
|
this.options = options;
|
|
42
42
|
this.llmConfig = llmConfig;
|
|
43
43
|
this.maxTokensPerModule = options.maxTokensPerModule ?? DEFAULT_MAX_TOKENS_PER_MODULE;
|
|
@@ -134,7 +134,7 @@ export class WikiGenerator {
|
|
|
134
134
|
}
|
|
135
135
|
// Init graph
|
|
136
136
|
this.onProgress('init', 2, 'Connecting to knowledge graph...');
|
|
137
|
-
await initWikiDb(this.
|
|
137
|
+
await initWikiDb(this.cgdbPath);
|
|
138
138
|
let result;
|
|
139
139
|
try {
|
|
140
140
|
if (!forceMode && existingMeta && existingMeta.fromCommit) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Graph Queries for Wiki Generation
|
|
3
3
|
*
|
|
4
4
|
* Encapsulated Cypher queries against the CodraGraph knowledge graph.
|
|
5
|
-
* Uses the MCP-style pooled
|
|
5
|
+
* Uses the MCP-style pooled cgdb-adapter for connection management.
|
|
6
6
|
*/
|
|
7
7
|
/**
|
|
8
8
|
* Touch the wiki DB connection to prevent idle timeout during long LLM calls.
|
|
@@ -36,7 +36,7 @@ export interface ProcessInfo {
|
|
|
36
36
|
/**
|
|
37
37
|
* Initialize the LadybugDB connection for wiki generation.
|
|
38
38
|
*/
|
|
39
|
-
export declare function initWikiDb(
|
|
39
|
+
export declare function initWikiDb(cgdbPath: string): Promise<void>;
|
|
40
40
|
/**
|
|
41
41
|
* Close the LadybugDB connection.
|
|
42
42
|
*/
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Graph Queries for Wiki Generation
|
|
3
3
|
*
|
|
4
4
|
* Encapsulated Cypher queries against the CodraGraph knowledge graph.
|
|
5
|
-
* Uses the MCP-style pooled
|
|
5
|
+
* Uses the MCP-style pooled cgdb-adapter for connection management.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { initCgdb, executeQuery, closeCgdb, touchRepo } from '../cgdb/pool-adapter.js';
|
|
8
8
|
const REPO_ID = '__wiki__';
|
|
9
9
|
/**
|
|
10
10
|
* Touch the wiki DB connection to prevent idle timeout during long LLM calls.
|
|
@@ -15,14 +15,14 @@ export function touchWikiDb() {
|
|
|
15
15
|
/**
|
|
16
16
|
* Initialize the LadybugDB connection for wiki generation.
|
|
17
17
|
*/
|
|
18
|
-
export async function initWikiDb(
|
|
19
|
-
await
|
|
18
|
+
export async function initWikiDb(cgdbPath) {
|
|
19
|
+
await initCgdb(REPO_ID, cgdbPath);
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
22
|
* Close the LadybugDB connection.
|
|
23
23
|
*/
|
|
24
24
|
export async function closeWikiDb() {
|
|
25
|
-
await
|
|
25
|
+
await closeCgdb(REPO_ID);
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* Get all source files with their exported symbol names and types.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { pipeline, env } from '@huggingface/transformers';
|
|
8
8
|
import { isHttpMode, getHttpDimensions, httpEmbedQuery, } from '../../core/embeddings/http-client.js';
|
|
9
|
-
import { silenceStdout, restoreStdout, realStderrWrite } from '../../core/
|
|
9
|
+
import { silenceStdout, restoreStdout, realStderrWrite } from '../../core/cgdb/pool-adapter.js';
|
|
10
10
|
// Model config
|
|
11
11
|
const MODEL_ID = 'Snowflake/snowflake-arctic-embed-xs';
|
|
12
12
|
// Module-level state for singleton pattern
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Supports multiple indexed repositories via a global registry.
|
|
6
6
|
* LadybugDB connections are opened lazily per repo on first query.
|
|
7
7
|
*/
|
|
8
|
-
import { isWriteQuery } from '../../core/
|
|
8
|
+
import { isWriteQuery } from '../../core/cgdb/pool-adapter.js';
|
|
9
9
|
export { isWriteQuery };
|
|
10
10
|
import { type RegistryEntry } from '../../storage/repo-manager.js';
|
|
11
11
|
import { GroupService } from '../../core/group/service.js';
|
|
@@ -53,7 +53,7 @@ interface RepoHandle {
|
|
|
53
53
|
name: string;
|
|
54
54
|
repoPath: string;
|
|
55
55
|
storagePath: string;
|
|
56
|
-
|
|
56
|
+
cgdbPath: string;
|
|
57
57
|
indexedAt: string;
|
|
58
58
|
lastCommit: string;
|
|
59
59
|
remoteUrl?: string;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
9
|
import path from 'path';
|
|
10
|
-
import {
|
|
10
|
+
import { initCgdb, executeQuery, executeParameterized, closeCgdb, isCgdbReady, isWriteQuery, } from '../../core/cgdb/pool-adapter.js';
|
|
11
11
|
export { isWriteQuery };
|
|
12
12
|
// Embedding imports are lazy (dynamic import) to avoid loading onnxruntime-node
|
|
13
13
|
// at MCP server startup — crashes on unsupported Node ABI versions (#89)
|
|
@@ -18,8 +18,8 @@ import { listRegisteredRepos, cleanupOldKuzuFiles, } from '../../storage/repo-ma
|
|
|
18
18
|
import { GroupService } from '../../core/group/service.js';
|
|
19
19
|
import { resolveAtGroupMemberRepoPath } from '../../core/group/resolve-at-member.js';
|
|
20
20
|
import { collectBestChunks } from '../../core/embeddings/types.js';
|
|
21
|
-
import { EMBEDDING_TABLE_NAME, EMBEDDING_INDEX_NAME } from '../../core/
|
|
22
|
-
import { decodeContentField } from '../../core/
|
|
21
|
+
import { EMBEDDING_TABLE_NAME, EMBEDDING_INDEX_NAME } from '../../core/cgdb/schema.js';
|
|
22
|
+
import { decodeContentField } from '../../core/cgdb/content-read.js';
|
|
23
23
|
import { PhaseTimer } from '../../core/search/phase-timer.js';
|
|
24
24
|
import { checkStaleness, checkCwdMatch } from '../../core/git-staleness.js';
|
|
25
25
|
// AI context generation is CLI-only (codragraph analyze)
|
|
@@ -189,7 +189,7 @@ export class LocalBackend {
|
|
|
189
189
|
}
|
|
190
190
|
/** Close all pooled LadybugDB connections (CLI one-shot; optional for long-lived MCP). */
|
|
191
191
|
async dispose() {
|
|
192
|
-
await
|
|
192
|
+
await closeCgdb();
|
|
193
193
|
}
|
|
194
194
|
// ─── Initialization ──────────────────────────────────────────────
|
|
195
195
|
/**
|
|
@@ -212,9 +212,9 @@ export class LocalBackend {
|
|
|
212
212
|
const id = this.repoId(entry.name, entry.path);
|
|
213
213
|
freshIds.add(id);
|
|
214
214
|
const storagePath = entry.storagePath;
|
|
215
|
-
const
|
|
215
|
+
const cgdbPath = path.join(storagePath, 'cgdb');
|
|
216
216
|
// Clean up any leftover KuzuDB files from before the LadybugDB migration.
|
|
217
|
-
// If kuzu exists but
|
|
217
|
+
// If kuzu exists but cgdb doesn't, warn so the user knows to re-analyze.
|
|
218
218
|
const kuzu = await cleanupOldKuzuFiles(storagePath);
|
|
219
219
|
if (kuzu.found && kuzu.needsReindex) {
|
|
220
220
|
console.error(`CodraGraph: "${entry.name}" has a stale KuzuDB index. Run: codragraph analyze ${entry.path}`);
|
|
@@ -224,7 +224,7 @@ export class LocalBackend {
|
|
|
224
224
|
name: entry.name,
|
|
225
225
|
repoPath: entry.path,
|
|
226
226
|
storagePath,
|
|
227
|
-
|
|
227
|
+
cgdbPath,
|
|
228
228
|
indexedAt: entry.indexedAt,
|
|
229
229
|
lastCommit: entry.lastCommit,
|
|
230
230
|
remoteUrl: entry.remoteUrl,
|
|
@@ -364,7 +364,7 @@ export class LocalBackend {
|
|
|
364
364
|
// Check if the index was rebuilt since we opened the connection (#297).
|
|
365
365
|
// Throttle staleness checks to at most once per 5 seconds per repo to
|
|
366
366
|
// avoid an fs.readFile round-trip on every tool invocation.
|
|
367
|
-
if (this.initializedRepos.has(repoId) &&
|
|
367
|
+
if (this.initializedRepos.has(repoId) && isCgdbReady(repoId)) {
|
|
368
368
|
const now = Date.now();
|
|
369
369
|
const lastCheck = this.lastStalenessCheck.get(repoId) ?? 0;
|
|
370
370
|
if (now - lastCheck < 5000)
|
|
@@ -380,10 +380,10 @@ export class LocalBackend {
|
|
|
380
380
|
// callers both detect staleness and double-close the pool.
|
|
381
381
|
const reinit = (async () => {
|
|
382
382
|
try {
|
|
383
|
-
await
|
|
383
|
+
await closeCgdb(repoId);
|
|
384
384
|
this.initializedRepos.delete(repoId);
|
|
385
385
|
handle.indexedAt = meta.indexedAt;
|
|
386
|
-
await
|
|
386
|
+
await initCgdb(repoId, handle.cgdbPath);
|
|
387
387
|
this.initializedRepos.add(repoId);
|
|
388
388
|
}
|
|
389
389
|
finally {
|
|
@@ -402,7 +402,7 @@ export class LocalBackend {
|
|
|
402
402
|
}
|
|
403
403
|
}
|
|
404
404
|
try {
|
|
405
|
-
await
|
|
405
|
+
await initCgdb(repoId, handle.cgdbPath);
|
|
406
406
|
this.initializedRepos.add(repoId);
|
|
407
407
|
}
|
|
408
408
|
catch (err) {
|
|
@@ -948,10 +948,10 @@ export class LocalBackend {
|
|
|
948
948
|
* BM25 keyword search helper - uses LadybugDB FTS for always-fresh results
|
|
949
949
|
*/
|
|
950
950
|
async bm25Search(repo, query, limit) {
|
|
951
|
-
const {
|
|
951
|
+
const { searchFTSFromCgdb } = await import('../../core/search/bm25-index.js');
|
|
952
952
|
let bm25Results;
|
|
953
953
|
try {
|
|
954
|
-
bm25Results = await
|
|
954
|
+
bm25Results = await searchFTSFromCgdb(query, limit, repo.id);
|
|
955
955
|
}
|
|
956
956
|
catch (err) {
|
|
957
957
|
console.error('CodraGraph: BM25/FTS search failed (FTS indexes may not exist) -', err.message);
|
|
@@ -1088,7 +1088,7 @@ export class LocalBackend {
|
|
|
1088
1088
|
}
|
|
1089
1089
|
async cypher(repo, params) {
|
|
1090
1090
|
await this.ensureInitialized(repo.id);
|
|
1091
|
-
if (!
|
|
1091
|
+
if (!isCgdbReady(repo.id)) {
|
|
1092
1092
|
return { error: 'LadybugDB not ready. Index may be corrupted.' };
|
|
1093
1093
|
}
|
|
1094
1094
|
// Block write operations (defense-in-depth — DB is already read-only)
|
|
@@ -3222,7 +3222,7 @@ export class LocalBackend {
|
|
|
3222
3222
|
};
|
|
3223
3223
|
}
|
|
3224
3224
|
async disconnect() {
|
|
3225
|
-
await
|
|
3225
|
+
await closeCgdb(); // close all connections
|
|
3226
3226
|
// Note: we intentionally do NOT call disposeEmbedder() here.
|
|
3227
3227
|
// ONNX Runtime's native cleanup segfaults on macOS and some Linux configs,
|
|
3228
3228
|
// and importing the embedder module on Node v24+ crashes if onnxruntime
|
package/dist/mcp/server.js
CHANGED
|
@@ -15,7 +15,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
15
15
|
import { CompatibleStdioServerTransport } from './compatible-stdio-transport.js';
|
|
16
16
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListResourceTemplatesRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
17
17
|
import { CODRAGRAPH_TOOLS } from './tools.js';
|
|
18
|
-
import { realStdoutWrite } from './core/
|
|
18
|
+
import { realStdoutWrite } from './core/cgdb-adapter.js';
|
|
19
19
|
import { getResourceDefinitions, getResourceTemplates, readResource } from './resources.js';
|
|
20
20
|
/**
|
|
21
21
|
* Next-step hints appended to tool responses.
|
|
@@ -208,7 +208,7 @@ export function createMCPServer(backend) {
|
|
|
208
208
|
{ name: 'repo', description: 'Repository (omit if only one indexed)', required: false },
|
|
209
209
|
{
|
|
210
210
|
name: 'symbolId',
|
|
211
|
-
description: 'Stable symbol id (PK in the
|
|
211
|
+
description: 'Stable symbol id (PK in the cgdb node table)',
|
|
212
212
|
required: true,
|
|
213
213
|
},
|
|
214
214
|
{
|
|
@@ -484,7 +484,7 @@ Follow these steps:
|
|
|
484
484
|
export async function startMCPServer(backend) {
|
|
485
485
|
const server = createMCPServer(backend);
|
|
486
486
|
// Use the shared stdout reference captured at module-load time by the
|
|
487
|
-
//
|
|
487
|
+
// cgdb-adapter. Avoids divergence if anything patches stdout between
|
|
488
488
|
// module load and server start.
|
|
489
489
|
const _safeStdout = new Proxy(process.stdout, {
|
|
490
490
|
get(target, prop, receiver) {
|
package/dist/mcp/tools.js
CHANGED
|
@@ -806,7 +806,7 @@ Pair with codragraph_context first to get the symbol's stable id (e.g. \`fn:src/
|
|
|
806
806
|
type: 'object',
|
|
807
807
|
properties: {
|
|
808
808
|
repo: { type: 'string', description: 'Indexed repo (omit if only one).' },
|
|
809
|
-
symbolId: { type: 'string', description: 'Stable symbol id (PK in the
|
|
809
|
+
symbolId: { type: 'string', description: 'Stable symbol id (PK in the cgdb node table).' },
|
|
810
810
|
table: {
|
|
811
811
|
type: 'string',
|
|
812
812
|
description: 'Optional table hint to narrow the search (Function, Class, Method, Interface, …).',
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* Child -> Parent: { type: 'error', message: string }
|
|
12
12
|
*/
|
|
13
13
|
import { runFullAnalysis } from '../core/run-analyze.js';
|
|
14
|
-
import {
|
|
14
|
+
import { closeCgdb } from '../core/cgdb/cgdb-adapter.js';
|
|
15
15
|
function send(msg) {
|
|
16
16
|
process.send?.(msg);
|
|
17
17
|
}
|
|
@@ -28,7 +28,7 @@ process.on('unhandledRejection', (reason) => {
|
|
|
28
28
|
process.on('SIGTERM', async () => {
|
|
29
29
|
send({ type: 'error', message: 'Analysis cancelled (worker received SIGTERM)' });
|
|
30
30
|
try {
|
|
31
|
-
await
|
|
31
|
+
await closeCgdb();
|
|
32
32
|
}
|
|
33
33
|
catch { }
|
|
34
34
|
process.exit(0);
|
package/dist/server/api.js
CHANGED
|
@@ -13,11 +13,11 @@ import path from 'path';
|
|
|
13
13
|
import fs from 'fs/promises';
|
|
14
14
|
import { createRequire } from 'node:module';
|
|
15
15
|
import { loadMeta, listRegisteredRepos, getStoragePath } from '../storage/repo-manager.js';
|
|
16
|
-
import { executeQuery, executePrepared, executeWithReusedStatement, streamQuery,
|
|
17
|
-
import { isWriteQuery } from '../core/
|
|
18
|
-
import { decodeContentField } from '../core/
|
|
16
|
+
import { executeQuery, executePrepared, executeWithReusedStatement, streamQuery, closeCgdb, withCgdbDb, } from '../core/cgdb/cgdb-adapter.js';
|
|
17
|
+
import { isWriteQuery } from '../core/cgdb/pool-adapter.js';
|
|
18
|
+
import { decodeContentField } from '../core/cgdb/content-read.js';
|
|
19
19
|
import { NODE_TABLES } from '../_shared/index.js';
|
|
20
|
-
import {
|
|
20
|
+
import { searchFTSFromCgdb } from '../core/search/bm25-index.js';
|
|
21
21
|
import { hybridSearch } from '../core/search/hybrid-search.js';
|
|
22
22
|
// Embedding imports are lazy (dynamic import) to avoid loading onnxruntime-node
|
|
23
23
|
// at server startup — crashes on unsupported Node ABI versions (#89)
|
|
@@ -388,7 +388,7 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
388
388
|
const cleanupMcp = mountMCPEndpoints(app, backend);
|
|
389
389
|
const jobManager = new JobManager();
|
|
390
390
|
// Shared repo lock — prevents concurrent analyze + embed on the same repo path,
|
|
391
|
-
// which would corrupt LadybugDB (analyze calls
|
|
391
|
+
// which would corrupt LadybugDB (analyze calls closeCgdb + initCgdb while embed has queries in flight).
|
|
392
392
|
const activeRepoPaths = new Set();
|
|
393
393
|
const acquireRepoLock = (repoPath) => {
|
|
394
394
|
if (activeRepoPaths.has(repoPath)) {
|
|
@@ -571,7 +571,7 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
571
571
|
try {
|
|
572
572
|
// Close any open LadybugDB handle before deleting files
|
|
573
573
|
try {
|
|
574
|
-
await
|
|
574
|
+
await closeCgdb();
|
|
575
575
|
}
|
|
576
576
|
catch { }
|
|
577
577
|
// 1. Delete the .codragraph index/storage directory
|
|
@@ -895,21 +895,21 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
895
895
|
res.status(404).json({ error: 'Repository not found' });
|
|
896
896
|
return;
|
|
897
897
|
}
|
|
898
|
-
const
|
|
898
|
+
const cgdbPath = path.join(entry.storagePath, 'cgdb');
|
|
899
899
|
const includeContent = req.query.includeContent === 'true';
|
|
900
900
|
const stream = req.query.stream === 'true';
|
|
901
|
-
// Guard: when a repo has no materialized
|
|
902
|
-
// seeded CAS-only repos), or the
|
|
901
|
+
// Guard: when a repo has no materialized cgdb schema (fixture-
|
|
902
|
+
// seeded CAS-only repos), or the cgdb WAL is corrupt/stale from
|
|
903
903
|
// a prior failed analyze, LadybugDB native will abort with
|
|
904
904
|
// UNREACHABLE_CODE or an ANY-vector exception. Detect both
|
|
905
905
|
// shapes — missing file (cheap fs.access) AND empty/4096-byte
|
|
906
906
|
// schema-only file (fs.stat) — and return an empty graph so
|
|
907
907
|
// the dashboard doesn't blow up. The Graph tab keeps working
|
|
908
|
-
// for repos that actually have a real
|
|
909
|
-
const
|
|
908
|
+
// for repos that actually have a real cgdb.
|
|
909
|
+
const isCgdbMaterialized = await (async () => {
|
|
910
910
|
try {
|
|
911
|
-
const stat = await fs.stat(
|
|
912
|
-
// Schema-only
|
|
911
|
+
const stat = await fs.stat(cgdbPath);
|
|
912
|
+
// Schema-only cgdb is exactly 4096 bytes (one page, no real
|
|
913
913
|
// data). Real graphs are larger.
|
|
914
914
|
return stat.isFile() && stat.size > 4096;
|
|
915
915
|
}
|
|
@@ -917,14 +917,14 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
917
917
|
return false;
|
|
918
918
|
}
|
|
919
919
|
})();
|
|
920
|
-
if (!
|
|
920
|
+
if (!isCgdbMaterialized) {
|
|
921
921
|
if (stream) {
|
|
922
922
|
res.setHeader('Content-Type', 'application/x-ndjson; charset=utf-8');
|
|
923
923
|
res.flushHeaders();
|
|
924
924
|
res.write(JSON.stringify({
|
|
925
925
|
type: 'meta',
|
|
926
926
|
repoName: entry.name,
|
|
927
|
-
note: 'no
|
|
927
|
+
note: 'no cgdb file — graph not yet materialized',
|
|
928
928
|
nodeCount: 0,
|
|
929
929
|
relationshipCount: 0,
|
|
930
930
|
}) + '\n');
|
|
@@ -937,7 +937,7 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
937
937
|
nodes: [],
|
|
938
938
|
relationships: [],
|
|
939
939
|
stats: { nodes: 0, edges: 0 },
|
|
940
|
-
note: 'no
|
|
940
|
+
note: 'no cgdb file — graph not yet materialized',
|
|
941
941
|
});
|
|
942
942
|
return;
|
|
943
943
|
}
|
|
@@ -959,7 +959,7 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
959
959
|
res.once('finish', markFinished);
|
|
960
960
|
res.once('close', abortStreaming);
|
|
961
961
|
try {
|
|
962
|
-
await
|
|
962
|
+
await withCgdbDb(cgdbPath, async () => streamGraphNdjson(res, includeContent, abortController.signal));
|
|
963
963
|
if (!abortController.signal.aborted && !res.writableEnded) {
|
|
964
964
|
res.end();
|
|
965
965
|
}
|
|
@@ -971,7 +971,7 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
971
971
|
}
|
|
972
972
|
return;
|
|
973
973
|
}
|
|
974
|
-
const graph = await
|
|
974
|
+
const graph = await withCgdbDb(cgdbPath, async () => buildGraph(includeContent));
|
|
975
975
|
res.json(graph);
|
|
976
976
|
}
|
|
977
977
|
catch (err) {
|
|
@@ -1009,8 +1009,8 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1009
1009
|
res.status(404).json({ error: 'Repository not found' });
|
|
1010
1010
|
return;
|
|
1011
1011
|
}
|
|
1012
|
-
const
|
|
1013
|
-
const result = await
|
|
1012
|
+
const cgdbPath = path.join(entry.storagePath, 'cgdb');
|
|
1013
|
+
const result = await withCgdbDb(cgdbPath, () => executeQuery(cypher));
|
|
1014
1014
|
res.json({ result });
|
|
1015
1015
|
}
|
|
1016
1016
|
catch (err) {
|
|
@@ -1030,14 +1030,14 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1030
1030
|
res.status(404).json({ error: 'Repository not found' });
|
|
1031
1031
|
return;
|
|
1032
1032
|
}
|
|
1033
|
-
const
|
|
1033
|
+
const cgdbPath = path.join(entry.storagePath, 'cgdb');
|
|
1034
1034
|
const parsedLimit = Number(req.body.limit ?? 10);
|
|
1035
1035
|
const limit = Number.isFinite(parsedLimit)
|
|
1036
1036
|
? Math.max(1, Math.min(100, Math.trunc(parsedLimit)))
|
|
1037
1037
|
: 10;
|
|
1038
1038
|
const mode = req.body.mode ?? 'hybrid';
|
|
1039
1039
|
const enrich = req.body.enrich !== false; // default true
|
|
1040
|
-
const results = await
|
|
1040
|
+
const results = await withCgdbDb(cgdbPath, async () => {
|
|
1041
1041
|
let searchResults;
|
|
1042
1042
|
if (mode === 'semantic') {
|
|
1043
1043
|
const { isEmbedderReady } = await import('../core/embeddings/embedder.js');
|
|
@@ -1055,7 +1055,7 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1055
1055
|
}));
|
|
1056
1056
|
}
|
|
1057
1057
|
else if (mode === 'bm25') {
|
|
1058
|
-
searchResults = await
|
|
1058
|
+
searchResults = await searchFTSFromCgdb(query, limit);
|
|
1059
1059
|
searchResults = searchResults.map((r, i) => ({
|
|
1060
1060
|
...r,
|
|
1061
1061
|
rank: i + 1,
|
|
@@ -1070,7 +1070,7 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1070
1070
|
searchResults = await hybridSearch(query, limit, executeQuery, semSearch);
|
|
1071
1071
|
}
|
|
1072
1072
|
else {
|
|
1073
|
-
searchResults = await
|
|
1073
|
+
searchResults = await searchFTSFromCgdb(query, limit);
|
|
1074
1074
|
}
|
|
1075
1075
|
}
|
|
1076
1076
|
if (!enrich)
|
|
@@ -1230,8 +1230,8 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1230
1230
|
const results = [];
|
|
1231
1231
|
const repoRoot = path.resolve(entry.path);
|
|
1232
1232
|
// Get file paths from the graph (lightweight — no content loaded)
|
|
1233
|
-
const
|
|
1234
|
-
const fileRows = await
|
|
1233
|
+
const cgdbPath = path.join(entry.storagePath, 'cgdb');
|
|
1234
|
+
const fileRows = await withCgdbDb(cgdbPath, () => executeQuery(`MATCH (n:File) WHERE n.content IS NOT NULL RETURN n.filePath AS filePath`));
|
|
1235
1235
|
// Search files on disk one at a time (constant memory)
|
|
1236
1236
|
for (const row of fileRows) {
|
|
1237
1237
|
if (results.length >= limit)
|
|
@@ -1602,12 +1602,12 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1602
1602
|
// Run embedding pipeline asynchronously
|
|
1603
1603
|
(async () => {
|
|
1604
1604
|
try {
|
|
1605
|
-
const
|
|
1606
|
-
await
|
|
1605
|
+
const cgdbPath = path.join(entry.storagePath, 'cgdb');
|
|
1606
|
+
await withCgdbDb(cgdbPath, async () => {
|
|
1607
1607
|
const { runEmbeddingPipeline } = await import('../core/embeddings/embedding-pipeline.js');
|
|
1608
1608
|
// Fetch existing content hashes for incremental embedding.
|
|
1609
|
-
// Delegated to
|
|
1610
|
-
const { fetchExistingEmbeddingHashes } = await import('../core/
|
|
1609
|
+
// Delegated to cgdb-adapter which owns the DB query logic and legacy-fallback handling.
|
|
1610
|
+
const { fetchExistingEmbeddingHashes } = await import('../core/cgdb/cgdb-adapter.js');
|
|
1611
1611
|
const existingEmbeddings = await fetchExistingEmbeddingHashes(executeQuery);
|
|
1612
1612
|
if (existingEmbeddings && existingEmbeddings.size > 0) {
|
|
1613
1613
|
console.log(`[embed] ${existingEmbeddings.size} nodes already embedded — incremental run with content-hash comparison`);
|
|
@@ -1718,7 +1718,7 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1718
1718
|
jobManager.dispose();
|
|
1719
1719
|
embedJobManager.dispose();
|
|
1720
1720
|
await cleanupMcp();
|
|
1721
|
-
await
|
|
1721
|
+
await closeCgdb();
|
|
1722
1722
|
await backend.disconnect();
|
|
1723
1723
|
process.exit(0);
|
|
1724
1724
|
};
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
*/
|
|
38
38
|
export declare const canonicalizePath: (p: string) => string;
|
|
39
39
|
/**
|
|
40
|
-
* On-disk schema version for `.codragraph/
|
|
40
|
+
* On-disk schema version for `.codragraph/cgdb` and `.codragraph/meta.json`.
|
|
41
41
|
*
|
|
42
42
|
* 1 — pre-RFC-0001-Phase-2 layout. Node tables have `content STRING`
|
|
43
43
|
* but no `contentEncoding` column. Implicit/missing on existing
|
|
@@ -110,7 +110,7 @@ export interface RepoMeta {
|
|
|
110
110
|
export interface IndexedRepo {
|
|
111
111
|
repoPath: string;
|
|
112
112
|
storagePath: string;
|
|
113
|
-
|
|
113
|
+
cgdbPath: string;
|
|
114
114
|
metaPath: string;
|
|
115
115
|
meta: RepoMeta;
|
|
116
116
|
}
|
|
@@ -140,7 +140,7 @@ export declare const getStoragePath: (repoPath: string) => string;
|
|
|
140
140
|
*/
|
|
141
141
|
export declare const getStoragePaths: (repoPath: string) => {
|
|
142
142
|
storagePath: string;
|
|
143
|
-
|
|
143
|
+
cgdbPath: string;
|
|
144
144
|
metaPath: string;
|
|
145
145
|
};
|
|
146
146
|
/**
|
|
@@ -153,7 +153,7 @@ export declare const hasKuzuIndex: (storagePath: string) => Promise<boolean>;
|
|
|
153
153
|
*
|
|
154
154
|
* Returns:
|
|
155
155
|
* found — true if .codragraph/kuzu existed and was deleted
|
|
156
|
-
* needsReindex — true if kuzu existed but
|
|
156
|
+
* needsReindex — true if kuzu existed but cgdb does not (re-analyze required)
|
|
157
157
|
*
|
|
158
158
|
* Callers own the user-facing messaging; this function only deletes files.
|
|
159
159
|
*/
|
|
@@ -50,7 +50,7 @@ export const canonicalizePath = (p) => {
|
|
|
50
50
|
}
|
|
51
51
|
};
|
|
52
52
|
/**
|
|
53
|
-
* On-disk schema version for `.codragraph/
|
|
53
|
+
* On-disk schema version for `.codragraph/cgdb` and `.codragraph/meta.json`.
|
|
54
54
|
*
|
|
55
55
|
* 1 — pre-RFC-0001-Phase-2 layout. Node tables have `content STRING`
|
|
56
56
|
* but no `contentEncoding` column. Implicit/missing on existing
|
|
@@ -83,7 +83,7 @@ export const getStoragePaths = (repoPath) => {
|
|
|
83
83
|
const storagePath = getStoragePath(repoPath);
|
|
84
84
|
return {
|
|
85
85
|
storagePath,
|
|
86
|
-
|
|
86
|
+
cgdbPath: path.join(storagePath, 'cgdb'),
|
|
87
87
|
metaPath: path.join(storagePath, 'meta.json'),
|
|
88
88
|
};
|
|
89
89
|
};
|
|
@@ -105,16 +105,16 @@ export const hasKuzuIndex = async (storagePath) => {
|
|
|
105
105
|
*
|
|
106
106
|
* Returns:
|
|
107
107
|
* found — true if .codragraph/kuzu existed and was deleted
|
|
108
|
-
* needsReindex — true if kuzu existed but
|
|
108
|
+
* needsReindex — true if kuzu existed but cgdb does not (re-analyze required)
|
|
109
109
|
*
|
|
110
110
|
* Callers own the user-facing messaging; this function only deletes files.
|
|
111
111
|
*/
|
|
112
112
|
export const cleanupOldKuzuFiles = async (storagePath) => {
|
|
113
113
|
const oldPath = path.join(storagePath, 'kuzu');
|
|
114
|
-
const newPath = path.join(storagePath, '
|
|
114
|
+
const newPath = path.join(storagePath, 'cgdb');
|
|
115
115
|
try {
|
|
116
116
|
await fs.stat(oldPath);
|
|
117
|
-
// Old kuzu file/dir exists — determine if
|
|
117
|
+
// Old kuzu file/dir exists — determine if cgdb is already present
|
|
118
118
|
let needsReindex = false;
|
|
119
119
|
try {
|
|
120
120
|
await fs.stat(newPath);
|
|
@@ -236,8 +236,8 @@ function handlePostToolUse(input) {
|
|
|
236
236
|
|
|
237
237
|
const cwd = input.cwd || process.cwd();
|
|
238
238
|
if (!path.isAbsolute(cwd)) return;
|
|
239
|
-
const
|
|
240
|
-
if (!
|
|
239
|
+
const codragraphDir = findCodraGraphDir(cwd);
|
|
240
|
+
if (!codragraphDir) return;
|
|
241
241
|
|
|
242
242
|
// Compare HEAD against last indexed commit — skip if unchanged
|
|
243
243
|
let currentHead = '';
|
|
@@ -258,7 +258,7 @@ function handlePostToolUse(input) {
|
|
|
258
258
|
let lastCommit = '';
|
|
259
259
|
let hadEmbeddings = false;
|
|
260
260
|
try {
|
|
261
|
-
const meta = JSON.parse(fs.readFileSync(path.join(
|
|
261
|
+
const meta = JSON.parse(fs.readFileSync(path.join(codragraphDir, 'meta.json'), 'utf-8'));
|
|
262
262
|
lastCommit = meta.lastCommit || '';
|
|
263
263
|
hadEmbeddings = meta.stats && meta.stats.embeddings > 0;
|
|
264
264
|
} catch {
|
|
@@ -282,7 +282,7 @@ function handlePostToolUse(input) {
|
|
|
282
282
|
// reindex is in flight. The spawned analyze removes it on exit (success or
|
|
283
283
|
// failure) via CODRAGRAPH_REINDEX_LOCK_PATH; the 10-min mtime fallback
|
|
284
284
|
// catches the rare crash that bypasses analyze's exit handler.
|
|
285
|
-
const coalescePath = path.join(
|
|
285
|
+
const coalescePath = path.join(codragraphDir, '.reindex.coalesce');
|
|
286
286
|
const crashSafetyTtlMs = 10 * 60 * 1000;
|
|
287
287
|
let inFlight = false;
|
|
288
288
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codragraph/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Anit Chaudhary",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"prepack": "node scripts/build.js"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@codragraph/graphstore": "^1.0
|
|
59
|
+
"@codragraph/graphstore": "^2.1.0",
|
|
60
60
|
"@huggingface/transformers": "^4.1.0",
|
|
61
61
|
"@ladybugdb/core": "^0.16.0",
|
|
62
62
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"tree-sitter-swift": "^0.6.0"
|
|
100
100
|
},
|
|
101
101
|
"devDependencies": {
|
|
102
|
-
"@codragraph/shared": "file:../
|
|
102
|
+
"@codragraph/shared": "file:../shared",
|
|
103
103
|
"@types/cli-progress": "^3.11.6",
|
|
104
104
|
"@types/cors": "^2.8.17",
|
|
105
105
|
"@types/express": "^4.17.21",
|