gitnexus 1.6.6-rc.2 → 1.6.6-rc.21
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/dist/_shared/index.d.ts +1 -1
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js.map +1 -1
- package/dist/_shared/scope-resolution/reference-site.d.ts +8 -0
- package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/registries/context.d.ts +18 -1
- package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/registries/context.js.map +1 -1
- package/dist/_shared/scope-resolution/registries/lookup-core.d.ts +4 -2
- package/dist/_shared/scope-resolution/registries/lookup-core.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/registries/lookup-core.js +8 -23
- package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -1
- package/dist/cli/analyze.js +65 -3
- package/dist/cli/eval-server.d.ts +14 -3
- package/dist/cli/eval-server.js +79 -9
- package/dist/cli/index.js +3 -1
- package/dist/cli/serve.js +6 -1
- package/dist/cli/setup.js +6 -4
- package/dist/cli/wiki.d.ts +1 -0
- package/dist/cli/wiki.js +32 -8
- package/dist/core/ingestion/languages/cpp/captures.js +165 -9
- package/dist/core/ingestion/languages/cpp/constraint-filter.js +102 -32
- package/dist/core/ingestion/languages/cpp/type-classifier.d.ts +9 -9
- package/dist/core/ingestion/languages/cpp/type-classifier.js +12 -8
- package/dist/core/ingestion/languages/java.js +35 -0
- package/dist/core/ingestion/languages/javascript/arity.d.ts +11 -0
- package/dist/core/ingestion/languages/javascript/arity.js +11 -0
- package/dist/core/ingestion/languages/javascript/captures.d.ts +30 -0
- package/dist/core/ingestion/languages/javascript/captures.js +655 -0
- package/dist/core/ingestion/languages/javascript/import-target.d.ts +29 -0
- package/dist/core/ingestion/languages/javascript/import-target.js +53 -0
- package/dist/core/ingestion/languages/javascript/index.d.ts +48 -0
- package/dist/core/ingestion/languages/javascript/index.js +48 -0
- package/dist/core/ingestion/languages/javascript/interpret.d.ts +29 -0
- package/dist/core/ingestion/languages/javascript/interpret.js +41 -0
- package/dist/core/ingestion/languages/javascript/merge-bindings.d.ts +16 -0
- package/dist/core/ingestion/languages/javascript/merge-bindings.js +18 -0
- package/dist/core/ingestion/languages/javascript/query.d.ts +52 -0
- package/dist/core/ingestion/languages/javascript/query.js +413 -0
- package/dist/core/ingestion/languages/javascript/scope-resolver.d.ts +37 -0
- package/dist/core/ingestion/languages/javascript/scope-resolver.js +73 -0
- package/dist/core/ingestion/languages/javascript/simple-hooks.d.ts +32 -0
- package/dist/core/ingestion/languages/javascript/simple-hooks.js +37 -0
- package/dist/core/ingestion/languages/typescript/simple-hooks.d.ts +10 -0
- package/dist/core/ingestion/languages/typescript/simple-hooks.js +4 -1
- package/dist/core/ingestion/languages/typescript.js +15 -0
- package/dist/core/ingestion/model/field-registry.d.ts +17 -3
- package/dist/core/ingestion/model/field-registry.js +19 -4
- package/dist/core/ingestion/model/owned-members-lookup.d.ts +19 -0
- package/dist/core/ingestion/model/owned-members-lookup.js +43 -0
- package/dist/core/ingestion/model/type-registry.d.ts +9 -0
- package/dist/core/ingestion/model/type-registry.js +18 -0
- package/dist/core/ingestion/registry-primary-flag.js +1 -0
- package/dist/core/ingestion/resolve-references.d.ts +3 -1
- package/dist/core/ingestion/resolve-references.js +5 -1
- package/dist/core/ingestion/scope-extractor.js +4 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +1 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +37 -7
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +2 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +8 -1
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +2 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +4 -2
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +48 -9
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +2 -0
- package/dist/core/lbug/lbug-adapter.d.ts +3 -1
- package/dist/core/lbug/lbug-adapter.js +33 -19
- package/dist/core/lbug/lbug-config.d.ts +1 -1
- package/dist/core/lbug/lbug-config.js +1 -1
- package/dist/core/lbug/pool-adapter.d.ts +19 -7
- package/dist/core/lbug/pool-adapter.js +70 -44
- package/dist/core/lbug/query-params.d.ts +1 -0
- package/dist/core/lbug/query-params.js +21 -0
- package/dist/core/search/bm25-index.js +4 -6
- package/dist/core/wiki/generator.d.ts +13 -0
- package/dist/core/wiki/generator.js +45 -4
- package/dist/core/wiki/llm-client.d.ts +1 -1
- package/dist/core/wiki/llm-client.js +23 -6
- package/dist/mcp/local/local-backend.d.ts +18 -3
- package/dist/mcp/local/local-backend.js +127 -11
- package/dist/mcp/tools.js +10 -0
- package/dist/server/api.d.ts +3 -0
- package/dist/server/api.js +58 -30
- package/package.json +3 -3
- package/web/assets/agent-SmDhIVj9.js +608 -0
- package/web/assets/{architectureDiagram-UL44E2DR-DvIkEGkp.js → architectureDiagram-UL44E2DR-ChS6kfgC.js} +1 -1
- package/web/assets/{chunk-LCXTWHL2-CqJ0oo1x.js → chunk-LCXTWHL2-CikfiTEP.js} +1 -1
- package/web/assets/{chunk-RG4AUYOV-D0M6vdq9.js → chunk-RG4AUYOV-D0pH0sP0.js} +1 -1
- package/web/assets/{classDiagram-KGZ6W3CR-BB-saqC1.js → classDiagram-KGZ6W3CR-VJjLBrQ3.js} +1 -1
- package/web/assets/{classDiagram-v2-72OJOZXJ-Bp2bAlS5.js → classDiagram-v2-72OJOZXJ-CL_v399n.js} +1 -1
- package/web/assets/{diagram-3NCE3AQN-BUxnDPOY.js → diagram-3NCE3AQN-DTTuOnDj.js} +1 -1
- package/web/assets/{diagram-GF46GFSD-BJTyqqvI.js → diagram-GF46GFSD-Di3e_png.js} +1 -1
- package/web/assets/{diagram-QXG6HAR7-DilMHamP.js → diagram-QXG6HAR7-CVQY1hhk.js} +1 -1
- package/web/assets/{diagram-WEQXMOUZ-8KUrarg1.js → diagram-WEQXMOUZ-BoxS7CN3.js} +1 -1
- package/web/assets/{erDiagram-L5TCEMPS-Doj9mSiW.js → erDiagram-L5TCEMPS-CLTuHusW.js} +1 -1
- package/web/assets/{flowDiagram-H6V6AXG4-BOSR-i3p.js → flowDiagram-H6V6AXG4-CQfVb2C4.js} +1 -1
- package/web/assets/{index-j4-NuleT.js → index-BuC2I_Cj.js} +5 -5
- package/web/assets/{infoDiagram-3YFTVSEB-Ce_TvtaV.js → infoDiagram-3YFTVSEB-BOk6LlXV.js} +1 -1
- package/web/assets/{ishikawaDiagram-BNXS4ZKH-4MXIDCSF.js → ishikawaDiagram-BNXS4ZKH-C0sASGB6.js} +1 -1
- package/web/assets/{kanban-definition-75IXJCU3-yYdfm8Kk.js → kanban-definition-75IXJCU3-DT9Lizpz.js} +1 -1
- package/web/assets/{mindmap-definition-2TDM6QVE-DFNEKM77.js → mindmap-definition-2TDM6QVE-BEYaB7hb.js} +1 -1
- package/web/assets/{pieDiagram-CU6KROY3-iZAqpvhd.js → pieDiagram-CU6KROY3-DCgHNkyY.js} +1 -1
- package/web/assets/{requirementDiagram-JXO7QTGE-CtDijhnf.js → requirementDiagram-JXO7QTGE-D6vDU9M2.js} +1 -1
- package/web/assets/{sequenceDiagram-VS2MUI6T-BdjvyCHM.js → sequenceDiagram-VS2MUI6T-CJrGCTXy.js} +1 -1
- package/web/assets/{stateDiagram-7D4R322I-e5odN9fG.js → stateDiagram-7D4R322I-BvxQnQyL.js} +1 -1
- package/web/assets/{stateDiagram-v2-36443NZ5-vPsvjR2D.js → stateDiagram-v2-36443NZ5-D8ziO9W2.js} +1 -1
- package/web/assets/{timeline-definition-O6YCAMPW-DPAksNQp.js → timeline-definition-O6YCAMPW-B45j2q3m.js} +1 -1
- package/web/assets/{vennDiagram-MWXL3ELB-Dtlfdt7J.js → vennDiagram-MWXL3ELB-CRkZRDhi.js} +1 -1
- package/web/assets/{wardleyDiagram-CUQ6CDDI-B23uRMUC.js → wardleyDiagram-CUQ6CDDI-BekUytZJ.js} +1 -1
- package/web/assets/{xychartDiagram-N2JHSOCM-DxwHeksW.js → xychartDiagram-N2JHSOCM-BeMqwSgr.js} +1 -1
- package/web/index.html +1 -1
- package/web/assets/agent-D2kLt6Dl.js +0 -597
|
@@ -7,14 +7,15 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
9
|
import path from 'path';
|
|
10
|
-
import { initLbug, executeQuery, executeParameterized, closeLbug, isLbugReady,
|
|
10
|
+
import { initLbug, executeQuery, executeParameterized, closeLbug, isLbugReady, } from '../../core/lbug/pool-adapter.js';
|
|
11
|
+
import { isValidQueryParams } from '../../core/lbug/query-params.js';
|
|
11
12
|
import { isWalCorruptionError, WAL_RECOVERY_SUGGESTION } from '../../core/lbug/lbug-config.js';
|
|
12
|
-
export { isWriteQuery };
|
|
13
13
|
// Embedding imports are lazy (dynamic import) to avoid loading onnxruntime-node
|
|
14
14
|
// at MCP server startup — crashes on unsupported Node ABI versions (#89)
|
|
15
15
|
// git utilities available if needed
|
|
16
16
|
// import { isGitRepo, getCurrentCommit, getGitRoot } from '../../storage/git.js';
|
|
17
|
-
import { parseDiffHunks } from '../../storage/git.js';
|
|
17
|
+
import { parseDiffHunks, getCanonicalRepoRoot, getGitRoot, } from '../../storage/git.js';
|
|
18
|
+
import { realpathSync } from 'fs';
|
|
18
19
|
import { listRegisteredRepos, cleanupOldKuzuFiles, } from '../../storage/repo-manager.js';
|
|
19
20
|
import { GroupService } from '../../core/group/service.js';
|
|
20
21
|
import { resolveAtGroupMemberRepoPath } from '../../core/group/resolve-at-member.js';
|
|
@@ -141,6 +142,7 @@ function logQueryError(context, err) {
|
|
|
141
142
|
const msg = err instanceof Error ? err.message : String(err);
|
|
142
143
|
logger.error({ context, err: msg }, 'GitNexus query failed');
|
|
143
144
|
}
|
|
145
|
+
const isReadOnlyDbError = (err) => /read-only database/i.test(err instanceof Error ? err.message : String(err));
|
|
144
146
|
/**
|
|
145
147
|
* Per-query latency telemetry for production aggregation (#553).
|
|
146
148
|
*
|
|
@@ -160,6 +162,77 @@ function logQueryTiming(query, phases) {
|
|
|
160
162
|
const truncated = query.length > 80 ? `${query.slice(0, 80)}…` : query;
|
|
161
163
|
logger.debug({ query: truncated, totalMs, phases }, 'GitNexus query timing');
|
|
162
164
|
}
|
|
165
|
+
/** Resolve symlinks for path comparison; falls back to path.resolve on error.
|
|
166
|
+
* Uses `realpathSync.native` (not the pure-JS `realpathSync`) so that Windows
|
|
167
|
+
* 8.3 short names (e.g. RUNNER~1 → runneradmin) are expanded to long form,
|
|
168
|
+
* matching the output of `git rev-parse --show-toplevel`. */
|
|
169
|
+
function tryRealpath(p) {
|
|
170
|
+
try {
|
|
171
|
+
return realpathSync.native(p);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return path.resolve(p);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Resolve the git diff cwd for detect_changes, auto-detecting linked worktrees.
|
|
179
|
+
*
|
|
180
|
+
* When `launchCwd` is a linked worktree of the same canonical repository as
|
|
181
|
+
* `repoPath` (i.e. `getGitRoot(launchCwd)` differs from `repoPath` but both
|
|
182
|
+
* share the same `getCanonicalRepoRoot`), returns the worktree's git root so
|
|
183
|
+
* that `git diff` sees the correct working directory and index.
|
|
184
|
+
*
|
|
185
|
+
* Returns `repoPath` unchanged in all other cases (non-worktree, git
|
|
186
|
+
* unavailable, unrelated repo).
|
|
187
|
+
*
|
|
188
|
+
* Extracted as a module-level export so tests can pass any `launchCwd` instead
|
|
189
|
+
* of relying on `process.cwd()`, which is fixed to the server launch directory
|
|
190
|
+
* and cannot be changed mid-process.
|
|
191
|
+
*/
|
|
192
|
+
export function resolveWorktreeCwd(repoPath, launchCwd) {
|
|
193
|
+
try {
|
|
194
|
+
// Verify repoPath is a git root before comparing against its canonical
|
|
195
|
+
// root. If getGitRoot returns a different path, repoPath is an arbitrary
|
|
196
|
+
// subdirectory — skip both the linked-worktree guard and auto-detection
|
|
197
|
+
// and fall through to the repoPath fallback.
|
|
198
|
+
const repoGitRoot = getGitRoot(repoPath);
|
|
199
|
+
const repoCanonical = repoGitRoot && tryRealpath(repoGitRoot) === tryRealpath(repoPath)
|
|
200
|
+
? getCanonicalRepoRoot(repoPath)
|
|
201
|
+
: null;
|
|
202
|
+
// Early exit: if repoPath is a linked worktree (differs from its canonical
|
|
203
|
+
// main-checkout root), return it unchanged. Do NOT override it with the
|
|
204
|
+
// server's launch directory — that would silently replace the explicitly-
|
|
205
|
+
// resolved worktree index with the main checkout.
|
|
206
|
+
//
|
|
207
|
+
// getCanonicalRepoRoot returns the main-checkout path for both the checkout
|
|
208
|
+
// and all linked worktrees:
|
|
209
|
+
// repoPath === canonical → main checkout (auto-detect may fire below)
|
|
210
|
+
// repoPath !== canonical → linked worktree (return as-is)
|
|
211
|
+
if (repoCanonical && tryRealpath(repoPath) !== tryRealpath(repoCanonical)) {
|
|
212
|
+
return repoPath;
|
|
213
|
+
}
|
|
214
|
+
const launchGitRoot = getGitRoot(launchCwd);
|
|
215
|
+
if (launchGitRoot) {
|
|
216
|
+
// Normalise via realpathSync before comparing so macOS /var → /private/var
|
|
217
|
+
// symlinks (and Windows 8.3 short names) don't create false mismatches.
|
|
218
|
+
const realLaunch = tryRealpath(launchGitRoot);
|
|
219
|
+
const realRepo = tryRealpath(repoPath);
|
|
220
|
+
if (realLaunch !== realRepo) {
|
|
221
|
+
const launchCanonical = getCanonicalRepoRoot(launchCwd);
|
|
222
|
+
// Use tryRealpath on both canonical values for cross-platform safety.
|
|
223
|
+
if (launchCanonical &&
|
|
224
|
+
repoCanonical &&
|
|
225
|
+
tryRealpath(launchCanonical) === tryRealpath(repoCanonical)) {
|
|
226
|
+
return launchGitRoot;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// Best-effort; fall through to repoPath.
|
|
233
|
+
}
|
|
234
|
+
return repoPath;
|
|
235
|
+
}
|
|
163
236
|
export class LocalBackend {
|
|
164
237
|
repos = new Map();
|
|
165
238
|
contextCache = new Map();
|
|
@@ -1017,27 +1090,31 @@ export class LocalBackend {
|
|
|
1017
1090
|
return [];
|
|
1018
1091
|
}
|
|
1019
1092
|
}
|
|
1020
|
-
async executeCypher(repoName, query) {
|
|
1093
|
+
async executeCypher(repoName, query, params = {}) {
|
|
1021
1094
|
const repo = await this.resolveRepo(repoName);
|
|
1022
|
-
return this.cypher(repo, { query });
|
|
1095
|
+
return this.cypher(repo, { query, params });
|
|
1023
1096
|
}
|
|
1024
|
-
async cypher(repo,
|
|
1097
|
+
async cypher(repo, request) {
|
|
1025
1098
|
await this.ensureInitialized(repo.id);
|
|
1026
1099
|
if (!isLbugReady(repo.id)) {
|
|
1027
1100
|
return { error: 'LadybugDB not ready. Index may be corrupted.' };
|
|
1028
1101
|
}
|
|
1029
|
-
|
|
1030
|
-
if (isWriteQuery(params.query)) {
|
|
1102
|
+
if (request.params !== undefined && !isValidQueryParams(request.params)) {
|
|
1031
1103
|
return {
|
|
1032
|
-
error: '
|
|
1104
|
+
error: '"params" must be a plain object with scalar values (string/number/boolean/null).',
|
|
1033
1105
|
};
|
|
1034
1106
|
}
|
|
1035
1107
|
try {
|
|
1036
|
-
const result = await
|
|
1108
|
+
const result = await executeParameterized(repo.id, request.query, request.params ?? {});
|
|
1037
1109
|
return result;
|
|
1038
1110
|
}
|
|
1039
1111
|
catch (err) {
|
|
1040
1112
|
const msg = err.message || 'Query failed';
|
|
1113
|
+
if (isReadOnlyDbError(err)) {
|
|
1114
|
+
return {
|
|
1115
|
+
error: 'Write operations (CREATE, DELETE, SET, MERGE, REMOVE, DROP, ALTER, COPY, DETACH) are not allowed. The knowledge graph is read-only.',
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1041
1118
|
if (isWalCorruptionError(err)) {
|
|
1042
1119
|
return {
|
|
1043
1120
|
error: msg,
|
|
@@ -1770,11 +1847,50 @@ export class LocalBackend {
|
|
|
1770
1847
|
}
|
|
1771
1848
|
let diffOutput;
|
|
1772
1849
|
try {
|
|
1850
|
+
// Resolve the cwd for git diff.
|
|
1851
|
+
//
|
|
1852
|
+
// In a linked worktree (e.g. /repo/wt-feature/), the user's staged and
|
|
1853
|
+
// unstaged changes live in that worktree's separate working directory and
|
|
1854
|
+
// index. Running `git diff` from the canonical repo root sees a different
|
|
1855
|
+
// working tree and returns empty output.
|
|
1856
|
+
//
|
|
1857
|
+
// Resolution order (see resolveWorktreeCwd for details):
|
|
1858
|
+
// 1. params.worktree — explicit override, validated against the
|
|
1859
|
+
// registered repo's canonical root.
|
|
1860
|
+
// 2. Auto-detect — if the server's launch cwd (process.cwd()) is a
|
|
1861
|
+
// linked worktree of the same canonical repo, use its git root.
|
|
1862
|
+
// 3. repo.repoPath — fallback (original behaviour, handled inside
|
|
1863
|
+
// resolveWorktreeCwd when no worktree is detected).
|
|
1864
|
+
//
|
|
1865
|
+
// Start with the auto-detected value; override with the validated
|
|
1866
|
+
// explicit param when provided. This avoids a dead initial assignment.
|
|
1867
|
+
let diffCwd = resolveWorktreeCwd(repo.repoPath, process.cwd());
|
|
1868
|
+
if (params.worktree) {
|
|
1869
|
+
if (!path.isAbsolute(params.worktree)) {
|
|
1870
|
+
return {
|
|
1871
|
+
error: `worktree must be an absolute path, got: "${params.worktree}"`,
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
const providedResolved = path.resolve(params.worktree);
|
|
1875
|
+
const repoCanonical = getCanonicalRepoRoot(repo.repoPath);
|
|
1876
|
+
if (!repoCanonical) {
|
|
1877
|
+
return {
|
|
1878
|
+
error: `Could not determine canonical root for repo "${repo.repoPath}". Is git available?`,
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
const worktreeCanonical = getCanonicalRepoRoot(providedResolved);
|
|
1882
|
+
if (!worktreeCanonical || tryRealpath(worktreeCanonical) !== tryRealpath(repoCanonical)) {
|
|
1883
|
+
return {
|
|
1884
|
+
error: `worktree "${params.worktree}" is not a worktree of repo "${repo.repoPath}". Ensure the path is inside the same git repository.`,
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
diffCwd = providedResolved;
|
|
1888
|
+
}
|
|
1773
1889
|
// maxBuffer raised from Node's 1MB default to 256MB to avoid ENOBUFS on
|
|
1774
1890
|
// repos with large unstaged/untracked diffs (e.g. unignored build folders).
|
|
1775
1891
|
// See issue: spawnSync git ENOBUFS in detect_changes(scope="unstaged").
|
|
1776
1892
|
diffOutput = execFileSync('git', diffArgs, {
|
|
1777
|
-
cwd:
|
|
1893
|
+
cwd: diffCwd,
|
|
1778
1894
|
encoding: 'utf-8',
|
|
1779
1895
|
maxBuffer: 256 * 1024 * 1024,
|
|
1780
1896
|
});
|
package/dist/mcp/tools.js
CHANGED
|
@@ -155,6 +155,10 @@ TIPS:
|
|
|
155
155
|
type: 'object',
|
|
156
156
|
properties: {
|
|
157
157
|
query: { type: 'string', description: 'Cypher query to execute' },
|
|
158
|
+
params: {
|
|
159
|
+
type: 'object',
|
|
160
|
+
description: 'Optional query parameters for placeholders (e.g. $name) to execute via prepared statement binding.',
|
|
161
|
+
},
|
|
158
162
|
repo: {
|
|
159
163
|
type: 'string',
|
|
160
164
|
description: 'Repository name or path. Omit if only one repo is indexed.',
|
|
@@ -218,6 +222,8 @@ Maps git diff hunks to indexed symbols, then traces which processes are impacted
|
|
|
218
222
|
WHEN TO USE: Before committing — to understand what your changes affect. Pre-commit review, PR preparation.
|
|
219
223
|
AFTER THIS: Review affected processes. Use context() on high-risk symbols. READ gitnexus://repo/{name}/process/{name} for full traces.
|
|
220
224
|
|
|
225
|
+
GIT WORKTREE SUPPORT: GitNexus automatically detects when the MCP server was launched from inside a linked git worktree and runs git diff against that worktree — no extra parameters needed in the common case. Pass "worktree" explicitly only when the server was started from a different directory than the worktree you are editing (e.g., the server runs from the canonical root but your changes are in a linked worktree at a different path).
|
|
226
|
+
|
|
221
227
|
Returns: changed symbols, affected processes, and a risk summary.`,
|
|
222
228
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
223
229
|
inputSchema: {
|
|
@@ -233,6 +239,10 @@ Returns: changed symbols, affected processes, and a risk summary.`,
|
|
|
233
239
|
type: 'string',
|
|
234
240
|
description: 'Branch/commit for "compare" scope (e.g., "main")',
|
|
235
241
|
},
|
|
242
|
+
worktree: {
|
|
243
|
+
type: 'string',
|
|
244
|
+
description: 'Absolute path to a linked git worktree. Pass this when your changes are in a worktree (the .git entry at that path is a file, not a directory). GitNexus will run git diff from that worktree so staged/unstaged changes are correctly detected.',
|
|
245
|
+
},
|
|
236
246
|
repo: {
|
|
237
247
|
type: 'string',
|
|
238
248
|
description: 'Repository name or path. Omit if only one repo is indexed.',
|
package/dist/server/api.d.ts
CHANGED
|
@@ -68,5 +68,8 @@ export declare const handleFileRequest: (req: {
|
|
|
68
68
|
};
|
|
69
69
|
json: (body: any) => void;
|
|
70
70
|
}, repoPath: string) => Promise<void>;
|
|
71
|
+
export declare const handleQueryRequest: (req: express.Request, res: express.Response, resolveRepo: (repoName?: string) => Promise<{
|
|
72
|
+
storagePath: string;
|
|
73
|
+
} | undefined>) => Promise<void>;
|
|
71
74
|
export declare const createServer: (port: number, host?: string) => Promise<void>;
|
|
72
75
|
export {};
|
package/dist/server/api.js
CHANGED
|
@@ -13,8 +13,8 @@ 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, flushWAL, closeLbug, withLbugDb, } from '../core/lbug/lbug-adapter.js';
|
|
17
|
-
import {
|
|
16
|
+
import { executeQuery, executePrepared, executeWithReusedStatement, streamQuery, flushWAL, closeLbug, withLbugDb, isReadOnlyDbError, } from '../core/lbug/lbug-adapter.js';
|
|
17
|
+
import { isValidQueryParams } from '../core/lbug/query-params.js';
|
|
18
18
|
import { NODE_TABLES } from '../_shared/index.js';
|
|
19
19
|
import { searchFTSFromLbug } from '../core/search/bm25-index.js';
|
|
20
20
|
import { hybridSearch } from '../core/search/hybrid-search.js';
|
|
@@ -377,7 +377,15 @@ export const streamGraphNdjson = async (res, includeContent = false, signal) =>
|
|
|
377
377
|
*/
|
|
378
378
|
const mountSSEProgress = (app, routePath, jm) => {
|
|
379
379
|
app.get(routePath, (req, res) => {
|
|
380
|
-
|
|
380
|
+
let jobId;
|
|
381
|
+
try {
|
|
382
|
+
jobId = assertString(req.params.jobId, 'jobId');
|
|
383
|
+
}
|
|
384
|
+
catch (err) {
|
|
385
|
+
res.status(err.status ?? 400).json({ error: err.message });
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const job = jm.getJob(jobId);
|
|
381
389
|
if (!job) {
|
|
382
390
|
res.status(404).json({ error: 'Job not found' });
|
|
383
391
|
return;
|
|
@@ -417,7 +425,7 @@ const mountSSEProgress = (app, routePath, jm) => {
|
|
|
417
425
|
try {
|
|
418
426
|
eventId++;
|
|
419
427
|
if (progress.phase === 'complete' || progress.phase === 'failed') {
|
|
420
|
-
const eventJob = jm.getJob(
|
|
428
|
+
const eventJob = jm.getJob(jobId);
|
|
421
429
|
res.write(`id: ${eventId}\nevent: ${progress.phase}\ndata: ${JSON.stringify({
|
|
422
430
|
repoName: eventJob?.repoName,
|
|
423
431
|
error: eventJob?.error,
|
|
@@ -533,6 +541,39 @@ export const handleFileRequest = async (req, res, repoPath) => {
|
|
|
533
541
|
}
|
|
534
542
|
}
|
|
535
543
|
};
|
|
544
|
+
export const handleQueryRequest = async (req, res, resolveRepo) => {
|
|
545
|
+
try {
|
|
546
|
+
const cypher = req.body.cypher;
|
|
547
|
+
if (!cypher) {
|
|
548
|
+
res.status(400).json({ error: 'Missing "cypher" in request body' });
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
const queryParams = req.body.params;
|
|
552
|
+
if (queryParams !== undefined && !isValidQueryParams(queryParams)) {
|
|
553
|
+
res.status(400).json({
|
|
554
|
+
error: '"params" must be a plain object with scalar values (string/number/boolean/null)',
|
|
555
|
+
});
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const entry = await resolveRepo(requestedRepo(req));
|
|
559
|
+
if (!entry) {
|
|
560
|
+
res.status(404).json({ error: 'Repository not found' });
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const lbugPath = path.join(entry.storagePath, 'lbug');
|
|
564
|
+
const result = await withLbugDb(lbugPath, () => executePrepared(cypher, queryParams ?? {}), {
|
|
565
|
+
readOnly: true,
|
|
566
|
+
});
|
|
567
|
+
res.json({ result });
|
|
568
|
+
}
|
|
569
|
+
catch (err) {
|
|
570
|
+
if (isReadOnlyDbError(err)) {
|
|
571
|
+
res.status(403).json({ error: 'Write queries are not allowed via the HTTP API' });
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
res.status(500).json({ error: err.message || 'Query failed' });
|
|
575
|
+
}
|
|
576
|
+
};
|
|
536
577
|
export const createServer = async (port, host = '127.0.0.1') => {
|
|
537
578
|
const app = express();
|
|
538
579
|
app.disable('x-powered-by');
|
|
@@ -857,7 +898,13 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
857
898
|
res.once('finish', markFinished);
|
|
858
899
|
res.once('close', abortStreaming);
|
|
859
900
|
try {
|
|
860
|
-
|
|
901
|
+
// Read-only open: /api/graph never writes. Write-mode opens engage
|
|
902
|
+
// LadybugDB's checkpoint machinery (`.shadow` sidecar), which on
|
|
903
|
+
// Windows races with the OS file handle release and trips
|
|
904
|
+
// "Cannot open file ... lbug.shadow - Error 2". See pool-adapter.ts
|
|
905
|
+
// which already opens read-only for the same reason, and the
|
|
906
|
+
// /api/query precedent in PR #1655.
|
|
907
|
+
await withLbugDb(lbugPath, async () => streamGraphNdjson(res, includeContent, abortController.signal), { readOnly: true });
|
|
861
908
|
if (!abortController.signal.aborted && !res.writableEnded) {
|
|
862
909
|
res.end();
|
|
863
910
|
}
|
|
@@ -869,7 +916,9 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
869
916
|
}
|
|
870
917
|
return;
|
|
871
918
|
}
|
|
872
|
-
const graph = await withLbugDb(lbugPath, async () => buildGraph(includeContent)
|
|
919
|
+
const graph = await withLbugDb(lbugPath, async () => buildGraph(includeContent), {
|
|
920
|
+
readOnly: true,
|
|
921
|
+
});
|
|
873
922
|
res.json(graph);
|
|
874
923
|
}
|
|
875
924
|
catch (err) {
|
|
@@ -892,28 +941,7 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
892
941
|
});
|
|
893
942
|
// Execute Cypher query
|
|
894
943
|
app.post('/api/query', async (req, res) => {
|
|
895
|
-
|
|
896
|
-
const cypher = req.body.cypher;
|
|
897
|
-
if (!cypher) {
|
|
898
|
-
res.status(400).json({ error: 'Missing "cypher" in request body' });
|
|
899
|
-
return;
|
|
900
|
-
}
|
|
901
|
-
if (isWriteQuery(cypher)) {
|
|
902
|
-
res.status(403).json({ error: 'Write queries are not allowed via the HTTP API' });
|
|
903
|
-
return;
|
|
904
|
-
}
|
|
905
|
-
const entry = await resolveRepo(requestedRepo(req));
|
|
906
|
-
if (!entry) {
|
|
907
|
-
res.status(404).json({ error: 'Repository not found' });
|
|
908
|
-
return;
|
|
909
|
-
}
|
|
910
|
-
const lbugPath = path.join(entry.storagePath, 'lbug');
|
|
911
|
-
const result = await withLbugDb(lbugPath, () => executeQuery(cypher));
|
|
912
|
-
res.json({ result });
|
|
913
|
-
}
|
|
914
|
-
catch (err) {
|
|
915
|
-
res.status(500).json({ error: err.message || 'Query failed' });
|
|
916
|
-
}
|
|
944
|
+
await handleQueryRequest(req, res, resolveRepo);
|
|
917
945
|
});
|
|
918
946
|
// Search (supports mode: 'hybrid' | 'semantic' | 'bm25', and optional enrichment)
|
|
919
947
|
app.post('/api/search', async (req, res) => {
|
|
@@ -1039,7 +1067,7 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1039
1067
|
return { ...r, ...enrichment };
|
|
1040
1068
|
}));
|
|
1041
1069
|
return { searchResults: enriched, ftsAvailable };
|
|
1042
|
-
});
|
|
1070
|
+
}, { readOnly: true });
|
|
1043
1071
|
const response = { results: results.searchResults ?? results };
|
|
1044
1072
|
if (results.ftsAvailable === false) {
|
|
1045
1073
|
response.warning =
|
|
@@ -1113,7 +1141,7 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1113
1141
|
const repoRoot = path.resolve(entry.path);
|
|
1114
1142
|
// Get file paths from the graph (lightweight — no content loaded)
|
|
1115
1143
|
const lbugPath = path.join(entry.storagePath, 'lbug');
|
|
1116
|
-
const fileRows = await withLbugDb(lbugPath, () => executeQuery(`MATCH (n:File) WHERE n.content IS NOT NULL RETURN n.filePath AS filePath`));
|
|
1144
|
+
const fileRows = await withLbugDb(lbugPath, () => executeQuery(`MATCH (n:File) WHERE n.content IS NOT NULL RETURN n.filePath AS filePath`), { readOnly: true });
|
|
1117
1145
|
// Search files on disk one at a time (constant memory)
|
|
1118
1146
|
for (const row of fileRows) {
|
|
1119
1147
|
if (results.length >= limit)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitnexus",
|
|
3
|
-
"version": "1.6.6-rc.
|
|
3
|
+
"version": "1.6.6-rc.21",
|
|
4
4
|
"description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
|
|
5
5
|
"author": "Abhigyan Patwari",
|
|
6
6
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"cli-progress": "^3.12.0",
|
|
61
61
|
"commander": "^14.0.3",
|
|
62
62
|
"cors": "^2.8.5",
|
|
63
|
-
"express": "^
|
|
63
|
+
"express": "^5.2.1",
|
|
64
64
|
"express-rate-limit": "^8.4.1",
|
|
65
65
|
"glob": "^13.0.6",
|
|
66
66
|
"graphology": "^0.26.0",
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
"devDependencies": {
|
|
101
101
|
"@types/cli-progress": "^3.11.6",
|
|
102
102
|
"@types/cors": "^2.8.17",
|
|
103
|
-
"@types/express": "^
|
|
103
|
+
"@types/express": "^5.0.6",
|
|
104
104
|
"@types/js-yaml": "^4.0.9",
|
|
105
105
|
"@types/node": "^25.6.0",
|
|
106
106
|
"@types/uuid": "^11.0.0",
|