@veewo/gitnexus 1.3.11 → 1.4.6-rc
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 +37 -80
- package/dist/benchmark/agent-context/tool-runner.js +2 -2
- package/dist/benchmark/neonspark-candidates.js +3 -3
- package/dist/benchmark/tool-runner.js +2 -2
- package/dist/cli/ai-context.d.ts +2 -1
- package/dist/cli/ai-context.js +16 -12
- package/dist/cli/analyze.d.ts +2 -0
- package/dist/cli/analyze.js +68 -48
- package/dist/cli/augment.js +1 -1
- package/dist/cli/eval-server.d.ts +8 -1
- package/dist/cli/eval-server.js +30 -13
- package/dist/cli/index.js +28 -82
- package/dist/cli/lazy-action.d.ts +6 -0
- package/dist/cli/lazy-action.js +18 -0
- package/dist/cli/mcp.js +3 -1
- package/dist/cli/setup.js +87 -48
- package/dist/cli/setup.test.js +18 -13
- package/dist/cli/skill-gen.d.ts +26 -0
- package/dist/cli/skill-gen.js +549 -0
- package/dist/cli/status.js +13 -4
- package/dist/cli/tool.d.ts +3 -2
- package/dist/cli/tool.js +50 -16
- package/dist/cli/wiki.js +8 -4
- package/dist/config/ignore-service.d.ts +25 -0
- package/dist/config/ignore-service.js +76 -0
- package/dist/config/supported-languages.d.ts +4 -1
- package/dist/config/supported-languages.js +3 -2
- package/dist/core/augmentation/engine.js +94 -67
- package/dist/core/embeddings/embedder.d.ts +1 -1
- package/dist/core/embeddings/embedder.js +1 -1
- package/dist/core/embeddings/embedding-pipeline.d.ts +3 -3
- package/dist/core/embeddings/embedding-pipeline.js +52 -25
- package/dist/core/embeddings/types.d.ts +1 -1
- package/dist/core/graph/types.d.ts +7 -2
- package/dist/core/ingestion/ast-cache.js +3 -2
- package/dist/core/ingestion/call-processor.d.ts +8 -6
- package/dist/core/ingestion/call-processor.js +468 -206
- package/dist/core/ingestion/call-routing.d.ts +53 -0
- package/dist/core/ingestion/call-routing.js +108 -0
- package/dist/core/ingestion/constants.d.ts +16 -0
- package/dist/core/ingestion/constants.js +16 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +2 -1
- package/dist/core/ingestion/entry-point-scoring.js +116 -23
- package/dist/core/ingestion/export-detection.d.ts +18 -0
- package/dist/core/ingestion/export-detection.js +231 -0
- package/dist/core/ingestion/filesystem-walker.js +4 -3
- package/dist/core/ingestion/framework-detection.d.ts +19 -4
- package/dist/core/ingestion/framework-detection.js +182 -6
- package/dist/core/ingestion/heritage-processor.d.ts +13 -5
- package/dist/core/ingestion/heritage-processor.js +109 -55
- package/dist/core/ingestion/import-processor.d.ts +16 -20
- package/dist/core/ingestion/import-processor.js +199 -579
- package/dist/core/ingestion/language-config.d.ts +46 -0
- package/dist/core/ingestion/language-config.js +167 -0
- package/dist/core/ingestion/mro-processor.d.ts +45 -0
- package/dist/core/ingestion/mro-processor.js +369 -0
- package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
- package/dist/core/ingestion/named-binding-extraction.js +363 -0
- package/dist/core/ingestion/parsing-processor.d.ts +4 -1
- package/dist/core/ingestion/parsing-processor.js +107 -109
- package/dist/core/ingestion/pipeline.d.ts +6 -3
- package/dist/core/ingestion/pipeline.js +208 -114
- package/dist/core/ingestion/process-processor.js +8 -2
- package/dist/core/ingestion/resolution-context.d.ts +53 -0
- package/dist/core/ingestion/resolution-context.js +132 -0
- package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
- package/dist/core/ingestion/resolvers/csharp.js +109 -0
- package/dist/core/ingestion/resolvers/go.d.ts +19 -0
- package/dist/core/ingestion/resolvers/go.js +42 -0
- package/dist/core/ingestion/resolvers/index.d.ts +18 -0
- package/dist/core/ingestion/resolvers/index.js +13 -0
- package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
- package/dist/core/ingestion/resolvers/jvm.js +87 -0
- package/dist/core/ingestion/resolvers/php.d.ts +15 -0
- package/dist/core/ingestion/resolvers/php.js +35 -0
- package/dist/core/ingestion/resolvers/python.d.ts +19 -0
- package/dist/core/ingestion/resolvers/python.js +52 -0
- package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
- package/dist/core/ingestion/resolvers/ruby.js +15 -0
- package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
- package/dist/core/ingestion/resolvers/rust.js +73 -0
- package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
- package/dist/core/ingestion/resolvers/standard.js +123 -0
- package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
- package/dist/core/ingestion/resolvers/utils.js +122 -0
- package/dist/core/ingestion/symbol-table.d.ts +21 -1
- package/dist/core/ingestion/symbol-table.js +40 -12
- package/dist/core/ingestion/tree-sitter-queries.d.ts +13 -10
- package/dist/core/ingestion/tree-sitter-queries.js +297 -7
- package/dist/core/ingestion/type-env.d.ts +49 -0
- package/dist/core/ingestion/type-env.js +611 -0
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +385 -0
- package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/csharp.js +383 -0
- package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/go.js +467 -0
- package/dist/core/ingestion/type-extractors/index.d.ts +22 -0
- package/dist/core/ingestion/type-extractors/index.js +31 -0
- package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
- package/dist/core/ingestion/type-extractors/jvm.js +681 -0
- package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/php.js +549 -0
- package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/python.js +406 -0
- package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/ruby.js +389 -0
- package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/rust.js +449 -0
- package/dist/core/ingestion/type-extractors/shared.d.ts +133 -0
- package/dist/core/ingestion/type-extractors/shared.js +703 -0
- package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/swift.js +137 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +127 -0
- package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/typescript.js +494 -0
- package/dist/core/ingestion/utils.d.ts +103 -0
- package/dist/core/ingestion/utils.js +1085 -4
- package/dist/core/ingestion/workers/parse-worker.d.ts +51 -4
- package/dist/core/ingestion/workers/parse-worker.js +634 -222
- package/dist/core/ingestion/workers/worker-pool.js +8 -0
- package/dist/core/{kuzu → lbug}/csv-generator.d.ts +12 -10
- package/dist/core/{kuzu → lbug}/csv-generator.js +82 -101
- package/dist/core/{kuzu/kuzu-adapter.d.ts → lbug/lbug-adapter.d.ts} +20 -25
- package/dist/core/{kuzu/kuzu-adapter.js → lbug/lbug-adapter.js} +150 -122
- package/dist/core/{kuzu → lbug}/schema.d.ts +4 -4
- package/dist/core/{kuzu → lbug}/schema.js +23 -22
- package/dist/core/lbug/schema.test.d.ts +1 -0
- package/dist/core/search/bm25-index.d.ts +4 -4
- package/dist/core/search/bm25-index.js +12 -11
- package/dist/core/search/hybrid-search.d.ts +2 -2
- package/dist/core/search/hybrid-search.js +6 -6
- package/dist/core/tree-sitter/parser-loader.d.ts +1 -0
- package/dist/core/tree-sitter/parser-loader.js +19 -0
- package/dist/core/wiki/generator.d.ts +2 -2
- package/dist/core/wiki/generator.js +6 -6
- package/dist/core/wiki/graph-queries.d.ts +4 -4
- package/dist/core/wiki/graph-queries.js +7 -7
- package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
- package/dist/mcp/compatible-stdio-transport.js +200 -0
- package/dist/mcp/core/{kuzu-adapter.d.ts → lbug-adapter.d.ts} +11 -10
- package/dist/mcp/core/lbug-adapter.js +327 -0
- package/dist/mcp/local/local-backend.d.ts +21 -16
- package/dist/mcp/local/local-backend.js +306 -706
- package/dist/mcp/local/unity-parity-seed-loader.d.ts +6 -1
- package/dist/mcp/local/unity-parity-seed-loader.js +119 -9
- package/dist/mcp/local/unity-parity-seed-loader.test.js +95 -7
- package/dist/mcp/resources.js +2 -2
- package/dist/mcp/server.js +28 -13
- package/dist/mcp/staleness.js +2 -2
- package/dist/mcp/tools.js +12 -3
- package/dist/server/api.js +12 -12
- package/dist/server/mcp-http.d.ts +1 -1
- package/dist/server/mcp-http.js +1 -1
- package/dist/storage/git.js +4 -1
- package/dist/storage/repo-manager.d.ts +20 -2
- package/dist/storage/repo-manager.js +74 -4
- package/dist/types/pipeline.d.ts +1 -1
- package/hooks/claude/gitnexus-hook.cjs +149 -46
- package/hooks/claude/pre-tool-use.sh +2 -1
- package/hooks/claude/session-start.sh +0 -0
- package/package.json +20 -4
- package/scripts/patch-tree-sitter-swift.cjs +74 -0
- package/skills/gitnexus-cli.md +8 -8
- package/skills/gitnexus-debugging.md +1 -1
- package/skills/gitnexus-exploring.md +1 -1
- package/skills/gitnexus-guide.md +1 -1
- package/skills/gitnexus-impact-analysis.md +1 -1
- package/skills/gitnexus-pr-review.md +163 -0
- package/skills/gitnexus-refactoring.md +1 -1
- package/dist/cli/claude-hooks.d.ts +0 -22
- package/dist/cli/claude-hooks.js +0 -97
- package/dist/mcp/core/kuzu-adapter.js +0 -231
- /package/dist/core/{kuzu/csv-generator.test.d.ts → ingestion/type-extractors/types.js} +0 -0
- /package/dist/core/{kuzu/relationship-pair-buckets.test.d.ts → lbug/csv-generator.test.d.ts} +0 -0
- /package/dist/core/{kuzu → lbug}/csv-generator.test.js +0 -0
- /package/dist/core/{kuzu → lbug}/relationship-pair-buckets.d.ts +0 -0
- /package/dist/core/{kuzu → lbug}/relationship-pair-buckets.js +0 -0
- /package/dist/core/{kuzu/schema.test.d.ts → lbug/relationship-pair-buckets.test.d.ts} +0 -0
- /package/dist/core/{kuzu → lbug}/relationship-pair-buckets.test.js +0 -0
- /package/dist/core/{kuzu → lbug}/schema.test.js +0 -0
|
@@ -1,34 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* LadybugDB Adapter (Connection Pool)
|
|
3
3
|
*
|
|
4
|
-
* Manages a pool of
|
|
4
|
+
* Manages a pool of LadybugDB databases keyed by repoId, each with
|
|
5
5
|
* multiple Connection objects for safe concurrent query execution.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* LadybugDB Connections are NOT thread-safe — a single Connection
|
|
8
8
|
* segfaults if concurrent .query() calls hit it simultaneously.
|
|
9
9
|
* This adapter provides a checkout/return connection pool so each
|
|
10
10
|
* concurrent query gets its own Connection from the same Database.
|
|
11
11
|
*
|
|
12
|
-
* @see https://docs.
|
|
12
|
+
* @see https://docs.ladybugdb.com/concurrency — multiple Connections
|
|
13
13
|
* from the same Database is the officially supported concurrency pattern.
|
|
14
14
|
*/
|
|
15
15
|
/**
|
|
16
16
|
* Initialize (or reuse) a Database + connection pool for a specific repo.
|
|
17
17
|
* Retries on lock errors (e.g., when `gitnexus analyze` is running).
|
|
18
18
|
*/
|
|
19
|
-
export declare const
|
|
19
|
+
export declare const initLbug: (repoId: string, dbPath: string) => Promise<void>;
|
|
20
|
+
export declare const executeQuery: (repoId: string, cypher: string) => Promise<any[]>;
|
|
20
21
|
/**
|
|
21
|
-
* Execute a query on a specific repo's connection pool.
|
|
22
|
-
*
|
|
22
|
+
* Execute a parameterized query on a specific repo's connection pool.
|
|
23
|
+
* Uses prepare/execute pattern to prevent Cypher injection.
|
|
23
24
|
*/
|
|
24
|
-
export declare const
|
|
25
|
+
export declare const executeParameterized: (repoId: string, cypher: string, params: Record<string, any>) => Promise<any[]>;
|
|
25
26
|
/**
|
|
26
27
|
* Close one or all repo pools.
|
|
27
28
|
* If repoId is provided, close only that repo's connections.
|
|
28
29
|
* If omitted, close all repos.
|
|
29
30
|
*/
|
|
30
|
-
export declare const
|
|
31
|
+
export declare const closeLbug: (repoId?: string) => Promise<void>;
|
|
31
32
|
/**
|
|
32
33
|
* Check if a specific repo's pool is active
|
|
33
34
|
*/
|
|
34
|
-
export declare const
|
|
35
|
+
export declare const isLbugReady: (repoId: string) => boolean;
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LadybugDB Adapter (Connection Pool)
|
|
3
|
+
*
|
|
4
|
+
* Manages a pool of LadybugDB databases keyed by repoId, each with
|
|
5
|
+
* multiple Connection objects for safe concurrent query execution.
|
|
6
|
+
*
|
|
7
|
+
* LadybugDB Connections are NOT thread-safe — a single Connection
|
|
8
|
+
* segfaults if concurrent .query() calls hit it simultaneously.
|
|
9
|
+
* This adapter provides a checkout/return connection pool so each
|
|
10
|
+
* concurrent query gets its own Connection from the same Database.
|
|
11
|
+
*
|
|
12
|
+
* @see https://docs.ladybugdb.com/concurrency — multiple Connections
|
|
13
|
+
* from the same Database is the officially supported concurrency pattern.
|
|
14
|
+
*/
|
|
15
|
+
import fs from 'fs/promises';
|
|
16
|
+
import lbug from '@ladybugdb/core';
|
|
17
|
+
import { getOpenLbugDatabase } from '../../core/lbug/lbug-adapter.js';
|
|
18
|
+
const pool = new Map();
|
|
19
|
+
const dbCache = new Map();
|
|
20
|
+
/** Max repos in the pool (LRU eviction) */
|
|
21
|
+
const MAX_POOL_SIZE = 5;
|
|
22
|
+
/** Idle timeout before closing a repo's connections */
|
|
23
|
+
const IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
24
|
+
/** Max connections per repo (caps concurrent queries per repo) */
|
|
25
|
+
const MAX_CONNS_PER_REPO = 8;
|
|
26
|
+
/** Connections created eagerly on init */
|
|
27
|
+
const INITIAL_CONNS_PER_REPO = 2;
|
|
28
|
+
let idleTimer = null;
|
|
29
|
+
/** Saved real stdout.write — used to silence LadybugDB native output without race conditions */
|
|
30
|
+
const realStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
31
|
+
let stdoutSilenceCount = 0;
|
|
32
|
+
/**
|
|
33
|
+
* Start the idle cleanup timer (runs every 60s)
|
|
34
|
+
*/
|
|
35
|
+
function ensureIdleTimer() {
|
|
36
|
+
if (idleTimer)
|
|
37
|
+
return;
|
|
38
|
+
idleTimer = setInterval(() => {
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
for (const [repoId, entry] of pool) {
|
|
41
|
+
if (now - entry.lastUsed > IDLE_TIMEOUT_MS && entry.checkedOut === 0) {
|
|
42
|
+
closeOne(repoId);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}, 60_000);
|
|
46
|
+
if (idleTimer && typeof idleTimer === 'object' && 'unref' in idleTimer) {
|
|
47
|
+
idleTimer.unref();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Evict the least-recently-used repo if pool is at capacity
|
|
52
|
+
*/
|
|
53
|
+
function evictLRU() {
|
|
54
|
+
if (pool.size < MAX_POOL_SIZE)
|
|
55
|
+
return;
|
|
56
|
+
let oldestId = null;
|
|
57
|
+
let oldestTime = Infinity;
|
|
58
|
+
for (const [id, entry] of pool) {
|
|
59
|
+
if (entry.checkedOut === 0 && entry.lastUsed < oldestTime) {
|
|
60
|
+
oldestTime = entry.lastUsed;
|
|
61
|
+
oldestId = id;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (oldestId) {
|
|
65
|
+
closeOne(oldestId);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Remove a repo from the pool and release its shared Database ref.
|
|
70
|
+
*
|
|
71
|
+
* LadybugDB's native .closeSync() triggers N-API destructor hooks that
|
|
72
|
+
* segfault on Linux/macOS. Pool databases are opened read-only, so
|
|
73
|
+
* there is no WAL to flush — just deleting the pool entry and letting
|
|
74
|
+
* the GC (or process exit) reclaim native resources is safe.
|
|
75
|
+
*/
|
|
76
|
+
function closeOne(repoId) {
|
|
77
|
+
const entry = pool.get(repoId);
|
|
78
|
+
if (entry) {
|
|
79
|
+
const shared = dbCache.get(entry.dbPath);
|
|
80
|
+
if (shared && shared.refCount > 0) {
|
|
81
|
+
shared.refCount--;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
pool.delete(repoId);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Create a new Connection from a repo's Database.
|
|
88
|
+
* Silences stdout to prevent native module output from corrupting MCP stdio.
|
|
89
|
+
*/
|
|
90
|
+
function silenceStdout() {
|
|
91
|
+
if (stdoutSilenceCount++ === 0) {
|
|
92
|
+
process.stdout.write = (() => true);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function restoreStdout() {
|
|
96
|
+
if (--stdoutSilenceCount <= 0) {
|
|
97
|
+
stdoutSilenceCount = 0;
|
|
98
|
+
process.stdout.write = realStdoutWrite;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function createConnection(db) {
|
|
102
|
+
silenceStdout();
|
|
103
|
+
try {
|
|
104
|
+
return new lbug.Connection(db);
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
restoreStdout();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Query timeout in milliseconds */
|
|
111
|
+
const QUERY_TIMEOUT_MS = 30_000;
|
|
112
|
+
/** Waiter queue timeout in milliseconds */
|
|
113
|
+
const WAITER_TIMEOUT_MS = 15_000;
|
|
114
|
+
const LOCK_RETRY_ATTEMPTS = 3;
|
|
115
|
+
const LOCK_RETRY_DELAY_MS = 2000;
|
|
116
|
+
const CYPHER_WRITE_RE = /\b(CREATE|DELETE|SET|MERGE|REMOVE|DROP|ALTER|COPY|DETACH)\b/i;
|
|
117
|
+
function assertReadOnlyQuery(cypher) {
|
|
118
|
+
if (CYPHER_WRITE_RE.test(cypher)) {
|
|
119
|
+
throw new Error('Write operations are not allowed. The knowledge graph is read-only.');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Initialize (or reuse) a Database + connection pool for a specific repo.
|
|
124
|
+
* Retries on lock errors (e.g., when `gitnexus analyze` is running).
|
|
125
|
+
*/
|
|
126
|
+
export const initLbug = async (repoId, dbPath) => {
|
|
127
|
+
const existing = pool.get(repoId);
|
|
128
|
+
if (existing) {
|
|
129
|
+
existing.lastUsed = Date.now();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Check if database exists
|
|
133
|
+
try {
|
|
134
|
+
await fs.stat(dbPath);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
throw new Error(`LadybugDB not found at ${dbPath}. Run: gitnexus analyze`);
|
|
138
|
+
}
|
|
139
|
+
evictLRU();
|
|
140
|
+
// Reuse an existing native Database if another repoId already opened this path.
|
|
141
|
+
// This prevents buffer manager exhaustion from multiple mmap regions on the same file.
|
|
142
|
+
let shared = dbCache.get(dbPath);
|
|
143
|
+
if (!shared) {
|
|
144
|
+
const coreDb = getOpenLbugDatabase(dbPath);
|
|
145
|
+
if (coreDb) {
|
|
146
|
+
shared = { db: coreDb, refCount: 0, ftsLoaded: true };
|
|
147
|
+
dbCache.set(dbPath, shared);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (!shared) {
|
|
151
|
+
// Open in read-only mode — MCP server never writes to the database.
|
|
152
|
+
// This allows multiple MCP server instances to read concurrently, and
|
|
153
|
+
// avoids lock conflicts when `gitnexus analyze` is writing.
|
|
154
|
+
let lastError = null;
|
|
155
|
+
for (let attempt = 1; attempt <= LOCK_RETRY_ATTEMPTS; attempt++) {
|
|
156
|
+
silenceStdout();
|
|
157
|
+
try {
|
|
158
|
+
const db = new lbug.Database(dbPath, 0, // bufferManagerSize (default)
|
|
159
|
+
false, // enableCompression (default)
|
|
160
|
+
true);
|
|
161
|
+
restoreStdout();
|
|
162
|
+
shared = { db, refCount: 0, ftsLoaded: false };
|
|
163
|
+
dbCache.set(dbPath, shared);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
restoreStdout();
|
|
168
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
169
|
+
const isLockError = lastError.message.includes('Could not set lock')
|
|
170
|
+
|| lastError.message.includes('lock');
|
|
171
|
+
if (!isLockError || attempt === LOCK_RETRY_ATTEMPTS)
|
|
172
|
+
break;
|
|
173
|
+
await new Promise(resolve => setTimeout(resolve, LOCK_RETRY_DELAY_MS * attempt));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (!shared) {
|
|
177
|
+
throw new Error(`LadybugDB unavailable for ${repoId}. Another process may be rebuilding the index. ` +
|
|
178
|
+
`Retry later. (${lastError?.message || 'unknown error'})`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
shared.refCount++;
|
|
182
|
+
const db = shared.db;
|
|
183
|
+
// Pre-create a small pool of connections
|
|
184
|
+
const available = [];
|
|
185
|
+
for (let i = 0; i < INITIAL_CONNS_PER_REPO; i++) {
|
|
186
|
+
available.push(createConnection(db));
|
|
187
|
+
}
|
|
188
|
+
pool.set(repoId, { db, available, checkedOut: 0, waiters: [], lastUsed: Date.now(), dbPath });
|
|
189
|
+
ensureIdleTimer();
|
|
190
|
+
// Load FTS extension once per shared Database
|
|
191
|
+
if (!shared.ftsLoaded) {
|
|
192
|
+
try {
|
|
193
|
+
await available[0].query('LOAD EXTENSION fts');
|
|
194
|
+
shared.ftsLoaded = true;
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// Extension may not be installed — FTS queries will fail gracefully
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* Checkout a connection from the pool.
|
|
203
|
+
* Returns an available connection, or creates a new one if under the cap.
|
|
204
|
+
* If all connections are busy and at cap, queues the caller until one is returned.
|
|
205
|
+
*/
|
|
206
|
+
function checkout(entry) {
|
|
207
|
+
// Fast path: grab an available connection
|
|
208
|
+
if (entry.available.length > 0) {
|
|
209
|
+
entry.checkedOut++;
|
|
210
|
+
return Promise.resolve(entry.available.pop());
|
|
211
|
+
}
|
|
212
|
+
// Grow the pool if under the cap
|
|
213
|
+
const totalConns = entry.available.length + entry.checkedOut;
|
|
214
|
+
if (totalConns < MAX_CONNS_PER_REPO) {
|
|
215
|
+
entry.checkedOut++;
|
|
216
|
+
return Promise.resolve(createConnection(entry.db));
|
|
217
|
+
}
|
|
218
|
+
// At capacity — queue the caller with a timeout.
|
|
219
|
+
return new Promise((resolve, reject) => {
|
|
220
|
+
const waiter = (conn) => {
|
|
221
|
+
clearTimeout(timer);
|
|
222
|
+
resolve(conn);
|
|
223
|
+
};
|
|
224
|
+
const timer = setTimeout(() => {
|
|
225
|
+
const idx = entry.waiters.indexOf(waiter);
|
|
226
|
+
if (idx !== -1)
|
|
227
|
+
entry.waiters.splice(idx, 1);
|
|
228
|
+
reject(new Error(`Connection pool exhausted: timed out after ${WAITER_TIMEOUT_MS}ms waiting for a free connection`));
|
|
229
|
+
}, WAITER_TIMEOUT_MS);
|
|
230
|
+
entry.waiters.push(waiter);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Return a connection to the pool after use.
|
|
235
|
+
* If there are queued waiters, hand the connection directly to the next one
|
|
236
|
+
* instead of putting it back in the available array (avoids race conditions).
|
|
237
|
+
*/
|
|
238
|
+
function checkin(entry, conn) {
|
|
239
|
+
if (entry.waiters.length > 0) {
|
|
240
|
+
// Hand directly to the next waiter — no intermediate available state
|
|
241
|
+
const waiter = entry.waiters.shift();
|
|
242
|
+
waiter(conn);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
entry.checkedOut--;
|
|
246
|
+
entry.available.push(conn);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Execute a query on a specific repo's connection pool.
|
|
251
|
+
* Automatically checks out a connection, runs the query, and returns it.
|
|
252
|
+
*/
|
|
253
|
+
/** Race a promise against a timeout */
|
|
254
|
+
function withTimeout(promise, ms, label) {
|
|
255
|
+
let timer;
|
|
256
|
+
const timeout = new Promise((_, reject) => {
|
|
257
|
+
timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
258
|
+
});
|
|
259
|
+
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
260
|
+
}
|
|
261
|
+
export const executeQuery = async (repoId, cypher) => {
|
|
262
|
+
assertReadOnlyQuery(cypher);
|
|
263
|
+
const entry = pool.get(repoId);
|
|
264
|
+
if (!entry) {
|
|
265
|
+
throw new Error(`LadybugDB not initialized for repo "${repoId}". Call initLbug first.`);
|
|
266
|
+
}
|
|
267
|
+
entry.lastUsed = Date.now();
|
|
268
|
+
const conn = await checkout(entry);
|
|
269
|
+
try {
|
|
270
|
+
const queryResult = await withTimeout(conn.query(cypher), QUERY_TIMEOUT_MS, 'Query');
|
|
271
|
+
const result = Array.isArray(queryResult) ? queryResult[0] : queryResult;
|
|
272
|
+
const rows = await result.getAll();
|
|
273
|
+
return rows;
|
|
274
|
+
}
|
|
275
|
+
finally {
|
|
276
|
+
checkin(entry, conn);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
/**
|
|
280
|
+
* Execute a parameterized query on a specific repo's connection pool.
|
|
281
|
+
* Uses prepare/execute pattern to prevent Cypher injection.
|
|
282
|
+
*/
|
|
283
|
+
export const executeParameterized = async (repoId, cypher, params) => {
|
|
284
|
+
assertReadOnlyQuery(cypher);
|
|
285
|
+
const entry = pool.get(repoId);
|
|
286
|
+
if (!entry) {
|
|
287
|
+
throw new Error(`LadybugDB not initialized for repo "${repoId}". Call initLbug first.`);
|
|
288
|
+
}
|
|
289
|
+
entry.lastUsed = Date.now();
|
|
290
|
+
const conn = await checkout(entry);
|
|
291
|
+
try {
|
|
292
|
+
const stmt = await withTimeout(conn.prepare(cypher), QUERY_TIMEOUT_MS, 'Prepare');
|
|
293
|
+
if (!stmt.isSuccess()) {
|
|
294
|
+
const errMsg = await stmt.getErrorMessage();
|
|
295
|
+
throw new Error(`Prepare failed: ${errMsg}`);
|
|
296
|
+
}
|
|
297
|
+
const queryResult = await withTimeout(conn.execute(stmt, params), QUERY_TIMEOUT_MS, 'Execute');
|
|
298
|
+
const result = Array.isArray(queryResult) ? queryResult[0] : queryResult;
|
|
299
|
+
const rows = await result.getAll();
|
|
300
|
+
return rows;
|
|
301
|
+
}
|
|
302
|
+
finally {
|
|
303
|
+
checkin(entry, conn);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
/**
|
|
307
|
+
* Close one or all repo pools.
|
|
308
|
+
* If repoId is provided, close only that repo's connections.
|
|
309
|
+
* If omitted, close all repos.
|
|
310
|
+
*/
|
|
311
|
+
export const closeLbug = async (repoId) => {
|
|
312
|
+
if (repoId) {
|
|
313
|
+
closeOne(repoId);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
for (const id of [...pool.keys()]) {
|
|
317
|
+
closeOne(id);
|
|
318
|
+
}
|
|
319
|
+
if (idleTimer) {
|
|
320
|
+
clearInterval(idleTimer);
|
|
321
|
+
idleTimer = null;
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
/**
|
|
325
|
+
* Check if a specific repo's pool is active
|
|
326
|
+
*/
|
|
327
|
+
export const isLbugReady = (repoId) => pool.has(repoId);
|
|
@@ -3,16 +3,29 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides tool implementations using local .gitnexus/ indexes.
|
|
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 {
|
|
9
|
-
import {
|
|
8
|
+
import type { ResolvedUnityBinding } from '../../core/unity/resolver.js';
|
|
9
|
+
import type { UnityContextPayload, UnityHydrationMeta } from './unity-enrichment.js';
|
|
10
10
|
import { type RegistryEntry } from '../../storage/repo-manager.js';
|
|
11
|
+
/**
|
|
12
|
+
* Quick test-file detection for filtering impact results.
|
|
13
|
+
* Matches common test file patterns across all supported languages.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isTestFilePath(filePath: string): boolean;
|
|
11
16
|
export declare function mergeUnityBindings(baseBindings: ResolvedUnityBinding[], resolvedByPath: Map<string, ResolvedUnityBinding[]>): ResolvedUnityBinding[];
|
|
12
17
|
export declare function mergeParityUnityBindings(baseNonLightweightBindings: ResolvedUnityBinding[], resolvedBindings: ResolvedUnityBinding[]): ResolvedUnityBinding[];
|
|
13
18
|
export declare function attachUnityHydrationMeta(payload: UnityContextPayload, input: Pick<UnityHydrationMeta, 'requestedMode' | 'effectiveMode' | 'elapsedMs' | 'fallbackToCompact'> & {
|
|
14
19
|
hasExpandableBindings: boolean;
|
|
15
20
|
}): UnityContextPayload;
|
|
21
|
+
/** Valid LadybugDB node labels for safe Cypher query construction */
|
|
22
|
+
export declare const VALID_NODE_LABELS: Set<string>;
|
|
23
|
+
/** Valid relation types for impact analysis filtering */
|
|
24
|
+
export declare const VALID_RELATION_TYPES: Set<string>;
|
|
25
|
+
/** Regex to detect write operations in user-supplied Cypher queries */
|
|
26
|
+
export declare const CYPHER_WRITE_RE: RegExp;
|
|
27
|
+
/** Check if a Cypher query contains write operations */
|
|
28
|
+
export declare function isWriteQuery(query: string): boolean;
|
|
16
29
|
export interface CodebaseContext {
|
|
17
30
|
projectName: string;
|
|
18
31
|
stats: {
|
|
@@ -27,7 +40,7 @@ interface RepoHandle {
|
|
|
27
40
|
name: string;
|
|
28
41
|
repoPath: string;
|
|
29
42
|
storagePath: string;
|
|
30
|
-
|
|
43
|
+
lbugPath: string;
|
|
31
44
|
indexedAt: string;
|
|
32
45
|
lastCommit: string;
|
|
33
46
|
stats?: RegistryEntry['stats'];
|
|
@@ -44,7 +57,7 @@ export declare class LocalBackend {
|
|
|
44
57
|
/**
|
|
45
58
|
* Re-read the global registry and update the in-memory repo map.
|
|
46
59
|
* New repos are added, existing repos are updated, removed repos are pruned.
|
|
47
|
-
*
|
|
60
|
+
* LadybugDB connections for removed repos are NOT closed (they idle-timeout naturally).
|
|
48
61
|
*/
|
|
49
62
|
private refreshRepos;
|
|
50
63
|
/**
|
|
@@ -94,7 +107,7 @@ export declare class LocalBackend {
|
|
|
94
107
|
*/
|
|
95
108
|
private query;
|
|
96
109
|
/**
|
|
97
|
-
* BM25 keyword search helper - uses
|
|
110
|
+
* BM25 keyword search helper - uses LadybugDB FTS for always-fresh results
|
|
98
111
|
*/
|
|
99
112
|
private bm25Search;
|
|
100
113
|
/**
|
|
@@ -111,7 +124,7 @@ export declare class LocalBackend {
|
|
|
111
124
|
/**
|
|
112
125
|
* Aggregate same-named clusters: group by heuristicLabel, sum symbols,
|
|
113
126
|
* weighted-average cohesion, filter out tiny clusters (<5 symbols).
|
|
114
|
-
* Raw communities stay intact in
|
|
127
|
+
* Raw communities stay intact in LadybugDB for Cypher queries.
|
|
115
128
|
*/
|
|
116
129
|
private aggregateClusters;
|
|
117
130
|
private overview;
|
|
@@ -126,15 +139,6 @@ export declare class LocalBackend {
|
|
|
126
139
|
* Routes cluster/process types to direct graph queries.
|
|
127
140
|
*/
|
|
128
141
|
private explore;
|
|
129
|
-
private hydrateUnityContext;
|
|
130
|
-
private buildParityWarmupKey;
|
|
131
|
-
private scheduleParityWarmup;
|
|
132
|
-
private shouldEnableParityWarmup;
|
|
133
|
-
private getOrRunParityHydration;
|
|
134
|
-
private computeParityPayload;
|
|
135
|
-
private hydrateUnityContextParity;
|
|
136
|
-
private hydrateUnityContextCompact;
|
|
137
|
-
private toUnityContextPayload;
|
|
138
142
|
/**
|
|
139
143
|
* Detect changes — git-diff based impact analysis.
|
|
140
144
|
* Maps changed lines to indexed symbols, then finds affected processes.
|
|
@@ -147,6 +151,7 @@ export declare class LocalBackend {
|
|
|
147
151
|
*/
|
|
148
152
|
private rename;
|
|
149
153
|
private impact;
|
|
154
|
+
private _impactImpl;
|
|
150
155
|
/**
|
|
151
156
|
* Query clusters (communities) directly from graph.
|
|
152
157
|
* Used by getClustersResource — avoids legacy overview() dispatch.
|