gitnexus 1.0.0 → 1.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 +56 -39
- package/dist/cli/ai-context.js +23 -18
- package/dist/cli/analyze.js +16 -3
- package/dist/cli/clean.d.ts +2 -0
- package/dist/cli/clean.js +34 -3
- package/dist/cli/index.js +9 -3
- package/dist/cli/list.d.ts +1 -1
- package/dist/cli/list.js +22 -19
- package/dist/cli/mcp.d.ts +3 -2
- package/dist/cli/mcp.js +27 -76
- package/dist/cli/setup.d.ts +8 -0
- package/dist/cli/setup.js +166 -0
- package/dist/core/search/bm25-index.d.ts +2 -1
- package/dist/core/search/bm25-index.js +50 -8
- package/dist/mcp/core/kuzu-adapter.d.ts +14 -12
- package/dist/mcp/core/kuzu-adapter.js +99 -35
- package/dist/mcp/local/local-backend.d.ts +61 -29
- package/dist/mcp/local/local-backend.js +256 -205
- package/dist/mcp/resources.d.ts +5 -4
- package/dist/mcp/resources.js +169 -64
- package/dist/mcp/server.d.ts +5 -3
- package/dist/mcp/server.js +7 -9
- package/dist/mcp/tools.d.ts +1 -1
- package/dist/mcp/tools.js +25 -2
- package/dist/storage/repo-manager.d.ts +42 -0
- package/dist/storage/repo-manager.js +99 -0
- package/package.json +1 -1
- package/skills/debugging.md +15 -13
- package/skills/exploring.md +34 -20
- package/skills/impact-analysis.md +14 -11
- package/skills/refactoring.md +16 -15
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup Command
|
|
3
|
+
*
|
|
4
|
+
* One-time global MCP configuration writer.
|
|
5
|
+
* Detects installed AI editors and writes the appropriate MCP config
|
|
6
|
+
* so the GitNexus MCP server is available in all projects.
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'fs/promises';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import os from 'os';
|
|
11
|
+
import { getGlobalDir } from '../storage/repo-manager.js';
|
|
12
|
+
/**
|
|
13
|
+
* The MCP server entry for all editors
|
|
14
|
+
*/
|
|
15
|
+
function getMcpEntry() {
|
|
16
|
+
return {
|
|
17
|
+
command: 'npx',
|
|
18
|
+
args: ['-y', 'gitnexus', 'mcp'],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Merge gitnexus entry into an existing MCP config JSON object.
|
|
23
|
+
* Returns the updated config.
|
|
24
|
+
*/
|
|
25
|
+
function mergeMcpConfig(existing) {
|
|
26
|
+
if (!existing || typeof existing !== 'object') {
|
|
27
|
+
existing = {};
|
|
28
|
+
}
|
|
29
|
+
if (!existing.mcpServers || typeof existing.mcpServers !== 'object') {
|
|
30
|
+
existing.mcpServers = {};
|
|
31
|
+
}
|
|
32
|
+
existing.mcpServers.gitnexus = getMcpEntry();
|
|
33
|
+
return existing;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Try to read a JSON file, returning null if it doesn't exist or is invalid.
|
|
37
|
+
*/
|
|
38
|
+
async function readJsonFile(filePath) {
|
|
39
|
+
try {
|
|
40
|
+
const raw = await fs.readFile(filePath, 'utf-8');
|
|
41
|
+
return JSON.parse(raw);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Write JSON to a file, creating parent directories if needed.
|
|
49
|
+
*/
|
|
50
|
+
async function writeJsonFile(filePath, data) {
|
|
51
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
52
|
+
await fs.writeFile(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Check if a directory exists
|
|
56
|
+
*/
|
|
57
|
+
async function dirExists(dirPath) {
|
|
58
|
+
try {
|
|
59
|
+
const stat = await fs.stat(dirPath);
|
|
60
|
+
return stat.isDirectory();
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// ─── Editor-specific setup ─────────────────────────────────────────
|
|
67
|
+
async function setupCursor(result) {
|
|
68
|
+
const cursorDir = path.join(os.homedir(), '.cursor');
|
|
69
|
+
if (!(await dirExists(cursorDir))) {
|
|
70
|
+
result.skipped.push('Cursor (not installed)');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const mcpPath = path.join(cursorDir, 'mcp.json');
|
|
74
|
+
try {
|
|
75
|
+
const existing = await readJsonFile(mcpPath);
|
|
76
|
+
const updated = mergeMcpConfig(existing);
|
|
77
|
+
await writeJsonFile(mcpPath, updated);
|
|
78
|
+
result.configured.push('Cursor');
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
result.errors.push(`Cursor: ${err.message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function setupClaudeCode(result) {
|
|
85
|
+
// Claude Code uses `claude mcp add` — we just print the command
|
|
86
|
+
// Check for common Claude Code indicators
|
|
87
|
+
const claudeDir = path.join(os.homedir(), '.claude');
|
|
88
|
+
const hasClaude = await dirExists(claudeDir);
|
|
89
|
+
if (!hasClaude) {
|
|
90
|
+
result.skipped.push('Claude Code (not installed)');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Claude Code uses a JSON settings file at ~/.claude.json or claude mcp add
|
|
94
|
+
console.log('');
|
|
95
|
+
console.log(' Claude Code detected. Run this command to add GitNexus:');
|
|
96
|
+
console.log('');
|
|
97
|
+
console.log(' claude mcp add gitnexus -- npx -y gitnexus mcp');
|
|
98
|
+
console.log('');
|
|
99
|
+
result.configured.push('Claude Code (manual step printed)');
|
|
100
|
+
}
|
|
101
|
+
async function setupOpenCode(result) {
|
|
102
|
+
const opencodeDir = path.join(os.homedir(), '.config', 'opencode');
|
|
103
|
+
if (!(await dirExists(opencodeDir))) {
|
|
104
|
+
result.skipped.push('OpenCode (not installed)');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const configPath = path.join(opencodeDir, 'config.json');
|
|
108
|
+
try {
|
|
109
|
+
const existing = await readJsonFile(configPath);
|
|
110
|
+
const config = existing || {};
|
|
111
|
+
if (!config.mcp)
|
|
112
|
+
config.mcp = {};
|
|
113
|
+
config.mcp.gitnexus = getMcpEntry();
|
|
114
|
+
await writeJsonFile(configPath, config);
|
|
115
|
+
result.configured.push('OpenCode');
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
result.errors.push(`OpenCode: ${err.message}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// ─── Main command ──────────────────────────────────────────────────
|
|
122
|
+
export const setupCommand = async () => {
|
|
123
|
+
console.log('');
|
|
124
|
+
console.log(' GitNexus Setup');
|
|
125
|
+
console.log(' ==============');
|
|
126
|
+
console.log('');
|
|
127
|
+
// Ensure global directory exists
|
|
128
|
+
const globalDir = getGlobalDir();
|
|
129
|
+
await fs.mkdir(globalDir, { recursive: true });
|
|
130
|
+
const result = {
|
|
131
|
+
configured: [],
|
|
132
|
+
skipped: [],
|
|
133
|
+
errors: [],
|
|
134
|
+
};
|
|
135
|
+
// Detect and configure each editor
|
|
136
|
+
await setupCursor(result);
|
|
137
|
+
await setupClaudeCode(result);
|
|
138
|
+
await setupOpenCode(result);
|
|
139
|
+
// Print results
|
|
140
|
+
if (result.configured.length > 0) {
|
|
141
|
+
console.log(' Configured:');
|
|
142
|
+
for (const name of result.configured) {
|
|
143
|
+
console.log(` + ${name}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (result.skipped.length > 0) {
|
|
147
|
+
console.log('');
|
|
148
|
+
console.log(' Skipped:');
|
|
149
|
+
for (const name of result.skipped) {
|
|
150
|
+
console.log(` - ${name}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (result.errors.length > 0) {
|
|
154
|
+
console.log('');
|
|
155
|
+
console.log(' Errors:');
|
|
156
|
+
for (const err of result.errors) {
|
|
157
|
+
console.log(` ! ${err}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
console.log('');
|
|
161
|
+
console.log(' Next steps:');
|
|
162
|
+
console.log(' 1. cd into any git repo');
|
|
163
|
+
console.log(' 2. Run: gitnexus analyze');
|
|
164
|
+
console.log(' 3. Open the repo in your editor — MCP is ready!');
|
|
165
|
+
console.log('');
|
|
166
|
+
};
|
|
@@ -17,6 +17,7 @@ export interface BM25SearchResult {
|
|
|
17
17
|
*
|
|
18
18
|
* @param query - Search query string
|
|
19
19
|
* @param limit - Maximum results
|
|
20
|
+
* @param repoId - If provided, queries will be routed via the MCP connection pool
|
|
20
21
|
* @returns Ranked search results from FTS indexes
|
|
21
22
|
*/
|
|
22
|
-
export declare const searchFTSFromKuzu: (query: string, limit?: number) => Promise<BM25SearchResult[]>;
|
|
23
|
+
export declare const searchFTSFromKuzu: (query: string, limit?: number, repoId?: string) => Promise<BM25SearchResult[]>;
|
|
@@ -5,6 +5,33 @@
|
|
|
5
5
|
* Always reads from the database (no cached state to drift).
|
|
6
6
|
*/
|
|
7
7
|
import { queryFTS } from '../kuzu/kuzu-adapter.js';
|
|
8
|
+
/**
|
|
9
|
+
* Execute a single FTS query via a custom executor (for MCP connection pool).
|
|
10
|
+
* Returns the same shape as core queryFTS.
|
|
11
|
+
*/
|
|
12
|
+
async function queryFTSViaExecutor(executor, tableName, indexName, query, limit) {
|
|
13
|
+
const escapedQuery = query.replace(/'/g, "''");
|
|
14
|
+
const cypher = `
|
|
15
|
+
CALL QUERY_FTS_INDEX('${tableName}', '${indexName}', '${escapedQuery}', conjunctive := false)
|
|
16
|
+
RETURN node, score
|
|
17
|
+
ORDER BY score DESC
|
|
18
|
+
LIMIT ${limit}
|
|
19
|
+
`;
|
|
20
|
+
try {
|
|
21
|
+
const rows = await executor(cypher);
|
|
22
|
+
return rows.map((row) => {
|
|
23
|
+
const node = row.node || row[0] || {};
|
|
24
|
+
const score = row.score ?? row[1] ?? 0;
|
|
25
|
+
return {
|
|
26
|
+
filePath: node.filePath || '',
|
|
27
|
+
score: typeof score === 'number' ? score : parseFloat(score) || 0,
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
8
35
|
/**
|
|
9
36
|
* Search using KuzuDB's built-in FTS (always fresh, reads from disk)
|
|
10
37
|
*
|
|
@@ -13,16 +40,31 @@ import { queryFTS } from '../kuzu/kuzu-adapter.js';
|
|
|
13
40
|
*
|
|
14
41
|
* @param query - Search query string
|
|
15
42
|
* @param limit - Maximum results
|
|
43
|
+
* @param repoId - If provided, queries will be routed via the MCP connection pool
|
|
16
44
|
* @returns Ranked search results from FTS indexes
|
|
17
45
|
*/
|
|
18
|
-
export const searchFTSFromKuzu = async (query, limit = 20) => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
46
|
+
export const searchFTSFromKuzu = async (query, limit = 20, repoId) => {
|
|
47
|
+
let fileResults, functionResults, classResults, methodResults;
|
|
48
|
+
if (repoId) {
|
|
49
|
+
// Use MCP connection pool via dynamic import
|
|
50
|
+
const { executeQuery } = await import('../../mcp/core/kuzu-adapter.js');
|
|
51
|
+
const executor = (cypher) => executeQuery(repoId, cypher);
|
|
52
|
+
[fileResults, functionResults, classResults, methodResults] = await Promise.all([
|
|
53
|
+
queryFTSViaExecutor(executor, 'File', 'file_fts', query, limit),
|
|
54
|
+
queryFTSViaExecutor(executor, 'Function', 'function_fts', query, limit),
|
|
55
|
+
queryFTSViaExecutor(executor, 'Class', 'class_fts', query, limit),
|
|
56
|
+
queryFTSViaExecutor(executor, 'Method', 'method_fts', query, limit),
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// Use core kuzu adapter (CLI / pipeline context)
|
|
61
|
+
[fileResults, functionResults, classResults, methodResults] = await Promise.all([
|
|
62
|
+
queryFTS('File', 'file_fts', query, limit, false).catch(() => []),
|
|
63
|
+
queryFTS('Function', 'function_fts', query, limit, false).catch(() => []),
|
|
64
|
+
queryFTS('Class', 'class_fts', query, limit, false).catch(() => []),
|
|
65
|
+
queryFTS('Method', 'method_fts', query, limit, false).catch(() => []),
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
26
68
|
// Merge results by filePath, summing scores for same file
|
|
27
69
|
const merged = new Map();
|
|
28
70
|
const addResults = (results) => {
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* KuzuDB Adapter (
|
|
2
|
+
* KuzuDB Adapter (Connection Pool)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Manages a pool of KuzuDB connections keyed by repoId.
|
|
5
|
+
* Connections are lazily opened on first query and evicted
|
|
6
|
+
* after idle timeout or when pool exceeds max size (LRU).
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
-
* Initialize
|
|
9
|
+
* Initialize (or reuse) a connection for a specific repo
|
|
10
10
|
*/
|
|
11
|
-
export declare const initKuzu: (
|
|
11
|
+
export declare const initKuzu: (repoId: string, dbPath: string) => Promise<void>;
|
|
12
12
|
/**
|
|
13
|
-
* Execute a query
|
|
13
|
+
* Execute a query on a specific repo's connection
|
|
14
14
|
*/
|
|
15
|
-
export declare const executeQuery: (cypher: string) => Promise<any[]>;
|
|
15
|
+
export declare const executeQuery: (repoId: string, cypher: string) => Promise<any[]>;
|
|
16
16
|
/**
|
|
17
|
-
* Close
|
|
17
|
+
* Close one or all connections.
|
|
18
|
+
* If repoId is provided, close only that connection.
|
|
19
|
+
* If omitted, close all connections in the pool.
|
|
18
20
|
*/
|
|
19
|
-
export declare const closeKuzu: () => Promise<void>;
|
|
21
|
+
export declare const closeKuzu: (repoId?: string) => Promise<void>;
|
|
20
22
|
/**
|
|
21
|
-
* Check if
|
|
23
|
+
* Check if a specific repo's connection is active
|
|
22
24
|
*/
|
|
23
|
-
export declare const isKuzuReady: () => boolean;
|
|
25
|
+
export declare const isKuzuReady: (repoId: string) => boolean;
|
|
@@ -1,62 +1,126 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* KuzuDB Adapter (
|
|
2
|
+
* KuzuDB Adapter (Connection Pool)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Manages a pool of KuzuDB connections keyed by repoId.
|
|
5
|
+
* Connections are lazily opened on first query and evicted
|
|
6
|
+
* after idle timeout or when pool exceeds max size (LRU).
|
|
7
7
|
*/
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
9
|
import kuzu from 'kuzu';
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const pool = new Map();
|
|
11
|
+
const MAX_POOL_SIZE = 5;
|
|
12
|
+
const IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
13
|
+
let idleTimer = null;
|
|
12
14
|
/**
|
|
13
|
-
*
|
|
15
|
+
* Start the idle cleanup timer (runs every 60s)
|
|
14
16
|
*/
|
|
15
|
-
|
|
16
|
-
if (
|
|
17
|
-
return;
|
|
17
|
+
function ensureIdleTimer() {
|
|
18
|
+
if (idleTimer)
|
|
19
|
+
return;
|
|
20
|
+
idleTimer = setInterval(() => {
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
for (const [repoId, entry] of pool) {
|
|
23
|
+
if (now - entry.lastUsed > IDLE_TIMEOUT_MS) {
|
|
24
|
+
closeOne(repoId);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}, 60_000);
|
|
28
|
+
// Don't keep the process alive just for this timer
|
|
29
|
+
if (idleTimer && typeof idleTimer === 'object' && 'unref' in idleTimer) {
|
|
30
|
+
idleTimer.unref();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Evict the least-recently-used connection if pool is at capacity
|
|
35
|
+
*/
|
|
36
|
+
function evictLRU() {
|
|
37
|
+
if (pool.size < MAX_POOL_SIZE)
|
|
38
|
+
return;
|
|
39
|
+
let oldestId = null;
|
|
40
|
+
let oldestTime = Infinity;
|
|
41
|
+
for (const [id, entry] of pool) {
|
|
42
|
+
if (entry.lastUsed < oldestTime) {
|
|
43
|
+
oldestTime = entry.lastUsed;
|
|
44
|
+
oldestId = id;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (oldestId) {
|
|
48
|
+
closeOne(oldestId);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Close a single pool entry
|
|
53
|
+
*/
|
|
54
|
+
function closeOne(repoId) {
|
|
55
|
+
const entry = pool.get(repoId);
|
|
56
|
+
if (!entry)
|
|
57
|
+
return;
|
|
58
|
+
try {
|
|
59
|
+
entry.conn.close();
|
|
60
|
+
}
|
|
61
|
+
catch { }
|
|
62
|
+
try {
|
|
63
|
+
entry.db.close();
|
|
64
|
+
}
|
|
65
|
+
catch { }
|
|
66
|
+
pool.delete(repoId);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Initialize (or reuse) a connection for a specific repo
|
|
70
|
+
*/
|
|
71
|
+
export const initKuzu = async (repoId, dbPath) => {
|
|
72
|
+
const existing = pool.get(repoId);
|
|
73
|
+
if (existing) {
|
|
74
|
+
existing.lastUsed = Date.now();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
18
77
|
// Check if database exists
|
|
19
78
|
try {
|
|
20
|
-
await fs.stat(
|
|
79
|
+
await fs.stat(dbPath);
|
|
21
80
|
}
|
|
22
81
|
catch {
|
|
23
|
-
throw new Error(`KuzuDB not found at ${
|
|
82
|
+
throw new Error(`KuzuDB not found at ${dbPath}. Run: gitnexus analyze`);
|
|
24
83
|
}
|
|
25
|
-
|
|
26
|
-
|
|
84
|
+
evictLRU();
|
|
85
|
+
const db = new kuzu.Database(dbPath);
|
|
86
|
+
const conn = new kuzu.Connection(db);
|
|
87
|
+
pool.set(repoId, { db, conn, lastUsed: Date.now(), dbPath });
|
|
88
|
+
ensureIdleTimer();
|
|
27
89
|
};
|
|
28
90
|
/**
|
|
29
|
-
* Execute a query
|
|
91
|
+
* Execute a query on a specific repo's connection
|
|
30
92
|
*/
|
|
31
|
-
export const executeQuery = async (cypher) => {
|
|
32
|
-
|
|
33
|
-
|
|
93
|
+
export const executeQuery = async (repoId, cypher) => {
|
|
94
|
+
const entry = pool.get(repoId);
|
|
95
|
+
if (!entry) {
|
|
96
|
+
throw new Error(`KuzuDB not initialized for repo "${repoId}". Call initKuzu first.`);
|
|
34
97
|
}
|
|
35
|
-
|
|
98
|
+
entry.lastUsed = Date.now();
|
|
99
|
+
const queryResult = await entry.conn.query(cypher);
|
|
36
100
|
const result = Array.isArray(queryResult) ? queryResult[0] : queryResult;
|
|
37
101
|
const rows = await result.getAll();
|
|
38
102
|
return rows;
|
|
39
103
|
};
|
|
40
104
|
/**
|
|
41
|
-
* Close
|
|
105
|
+
* Close one or all connections.
|
|
106
|
+
* If repoId is provided, close only that connection.
|
|
107
|
+
* If omitted, close all connections in the pool.
|
|
42
108
|
*/
|
|
43
|
-
export const closeKuzu = async () => {
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
catch { }
|
|
49
|
-
conn = null;
|
|
109
|
+
export const closeKuzu = async (repoId) => {
|
|
110
|
+
if (repoId) {
|
|
111
|
+
closeOne(repoId);
|
|
112
|
+
return;
|
|
50
113
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
114
|
+
// Close all
|
|
115
|
+
for (const id of [...pool.keys()]) {
|
|
116
|
+
closeOne(id);
|
|
117
|
+
}
|
|
118
|
+
if (idleTimer) {
|
|
119
|
+
clearInterval(idleTimer);
|
|
120
|
+
idleTimer = null;
|
|
57
121
|
}
|
|
58
122
|
};
|
|
59
123
|
/**
|
|
60
|
-
* Check if
|
|
124
|
+
* Check if a specific repo's connection is active
|
|
61
125
|
*/
|
|
62
|
-
export const isKuzuReady = () =>
|
|
126
|
+
export const isKuzuReady = (repoId) => pool.has(repoId);
|
|
@@ -1,29 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Local Backend
|
|
2
|
+
* Local Backend (Multi-Repo)
|
|
3
3
|
*
|
|
4
|
-
* Provides tool implementations using local .gitnexus/
|
|
5
|
-
*
|
|
4
|
+
* Provides tool implementations using local .gitnexus/ indexes.
|
|
5
|
+
* Supports multiple indexed repositories via a global registry.
|
|
6
|
+
* KuzuDB connections are opened lazily per repo on first query.
|
|
6
7
|
*/
|
|
7
|
-
|
|
8
|
-
repoPath: string;
|
|
9
|
-
lastCommit: string;
|
|
10
|
-
indexedAt: string;
|
|
11
|
-
stats?: {
|
|
12
|
-
files?: number;
|
|
13
|
-
nodes?: number;
|
|
14
|
-
edges?: number;
|
|
15
|
-
communities?: number;
|
|
16
|
-
processes?: number;
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
export interface IndexedRepo {
|
|
20
|
-
repoPath: string;
|
|
21
|
-
storagePath: string;
|
|
22
|
-
kuzuPath: string;
|
|
23
|
-
metaPath: string;
|
|
24
|
-
meta: RepoMeta;
|
|
25
|
-
}
|
|
26
|
-
export declare function findRepo(startPath: string): Promise<IndexedRepo | null>;
|
|
8
|
+
import { type RegistryEntry } from '../../storage/repo-manager.js';
|
|
27
9
|
export interface CodebaseContext {
|
|
28
10
|
projectName: string;
|
|
29
11
|
stats: {
|
|
@@ -43,17 +25,66 @@ export interface CodebaseContext {
|
|
|
43
25
|
}>;
|
|
44
26
|
folderTree: string;
|
|
45
27
|
}
|
|
28
|
+
interface RepoHandle {
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
repoPath: string;
|
|
32
|
+
storagePath: string;
|
|
33
|
+
kuzuPath: string;
|
|
34
|
+
indexedAt: string;
|
|
35
|
+
lastCommit: string;
|
|
36
|
+
stats?: RegistryEntry['stats'];
|
|
37
|
+
}
|
|
46
38
|
export declare class LocalBackend {
|
|
47
|
-
private
|
|
48
|
-
private
|
|
49
|
-
private
|
|
50
|
-
|
|
39
|
+
private repos;
|
|
40
|
+
private contextCache;
|
|
41
|
+
private initializedRepos;
|
|
42
|
+
/**
|
|
43
|
+
* Initialize from the global registry.
|
|
44
|
+
* Returns true if at least one repo is available.
|
|
45
|
+
*/
|
|
46
|
+
init(): Promise<boolean>;
|
|
47
|
+
/**
|
|
48
|
+
* Generate a stable repo ID from name + path.
|
|
49
|
+
* If names collide, append a hash of the path.
|
|
50
|
+
*/
|
|
51
|
+
private repoId;
|
|
52
|
+
/**
|
|
53
|
+
* Resolve which repo to use.
|
|
54
|
+
* - If repoParam is given, match by name or path
|
|
55
|
+
* - If only 1 repo, use it
|
|
56
|
+
* - If 0 or multiple without param, throw with helpful message
|
|
57
|
+
*/
|
|
58
|
+
resolveRepo(repoParam?: string): RepoHandle;
|
|
51
59
|
private ensureInitialized;
|
|
52
|
-
get context(): CodebaseContext | null;
|
|
53
60
|
get isReady(): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Get context for a specific repo (or the single repo if only one).
|
|
63
|
+
*/
|
|
64
|
+
getContext(repoId?: string): CodebaseContext | null;
|
|
65
|
+
/**
|
|
66
|
+
* Backwards-compatible getter — returns context of single repo or null.
|
|
67
|
+
*/
|
|
68
|
+
get context(): CodebaseContext | null;
|
|
69
|
+
getRepoPath(repoId?: string): string | null;
|
|
54
70
|
get repoPath(): string | null;
|
|
55
|
-
|
|
71
|
+
getMeta(repoId?: string): {
|
|
72
|
+
lastCommit: string;
|
|
73
|
+
indexedAt: string;
|
|
74
|
+
stats?: any;
|
|
75
|
+
} | null;
|
|
56
76
|
get meta(): any;
|
|
77
|
+
get storagePath(): string | null;
|
|
78
|
+
/**
|
|
79
|
+
* List all registered repos with their metadata.
|
|
80
|
+
*/
|
|
81
|
+
listRepos(): Array<{
|
|
82
|
+
name: string;
|
|
83
|
+
path: string;
|
|
84
|
+
indexedAt: string;
|
|
85
|
+
lastCommit: string;
|
|
86
|
+
stats?: any;
|
|
87
|
+
}>;
|
|
57
88
|
callTool(method: string, params: any): Promise<any>;
|
|
58
89
|
private search;
|
|
59
90
|
/**
|
|
@@ -71,3 +102,4 @@ export declare class LocalBackend {
|
|
|
71
102
|
private analyze;
|
|
72
103
|
disconnect(): Promise<void>;
|
|
73
104
|
}
|
|
105
|
+
export {};
|