gitnexus 1.6.4-rc.2 → 1.6.4-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/README.md +35 -0
- package/dist/_shared/index.d.ts +1 -1
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js +1 -1
- package/dist/_shared/index.js.map +1 -1
- package/dist/_shared/scope-resolution/finalize-algorithm.d.ts +22 -14
- package/dist/_shared/scope-resolution/finalize-algorithm.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/finalize-algorithm.js +298 -37
- package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.d.ts +23 -1
- package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.js +36 -2
- package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
- package/dist/_shared/scope-resolution/types.d.ts +47 -3
- package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/types.js +10 -2
- package/dist/_shared/scope-resolution/types.js.map +1 -1
- package/dist/cli/analyze.d.ts +6 -0
- package/dist/cli/analyze.js +35 -0
- package/dist/cli/doctor.d.ts +1 -0
- package/dist/cli/doctor.js +31 -0
- package/dist/cli/index.js +13 -0
- package/dist/cli/setup.js +2 -2
- package/dist/core/embeddings/config.d.ts +2 -0
- package/dist/core/embeddings/config.js +36 -0
- package/dist/core/embeddings/embedder.js +11 -6
- package/dist/core/embeddings/embedding-pipeline.d.ts +7 -1
- package/dist/core/embeddings/embedding-pipeline.js +93 -29
- package/dist/core/embeddings/exact-search.d.ts +15 -0
- package/dist/core/embeddings/exact-search.js +27 -0
- package/dist/core/embeddings/types.d.ts +4 -0
- package/dist/core/embeddings/types.js +2 -0
- package/dist/core/group/config-parser.js +2 -0
- package/dist/core/group/matching.d.ts +3 -3
- package/dist/core/group/matching.js +46 -6
- package/dist/core/group/storage.js +2 -0
- package/dist/core/group/sync.js +1 -1
- package/dist/core/group/types.d.ts +18 -0
- package/dist/core/ingestion/call-processor.d.ts +3 -3
- package/dist/core/ingestion/call-processor.js +58 -65
- package/dist/core/ingestion/constants.d.ts +4 -3
- package/dist/core/ingestion/constants.js +8 -3
- package/dist/core/ingestion/finalize-orchestrator.js +6 -3
- package/dist/core/ingestion/heritage-processor.js +2 -2
- package/dist/core/ingestion/import-processor.js +1 -1
- package/dist/core/ingestion/language-provider.d.ts +8 -0
- package/dist/core/ingestion/languages/csharp/captures.js +4 -1
- package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +14 -13
- package/dist/core/ingestion/languages/csharp/namespace-siblings.js +62 -50
- package/dist/core/ingestion/languages/python/captures.js +9 -1
- package/dist/core/ingestion/languages/python/index.d.ts +1 -1
- package/dist/core/ingestion/languages/python/index.js +1 -1
- package/dist/core/ingestion/languages/python/simple-hooks.d.ts +3 -1
- package/dist/core/ingestion/languages/python/simple-hooks.js +8 -0
- package/dist/core/ingestion/languages/python.js +28 -1
- package/dist/core/ingestion/languages/swift.js +14 -0
- package/dist/core/ingestion/languages/typescript/arity-metadata.d.ts +59 -0
- package/dist/core/ingestion/languages/typescript/arity-metadata.js +103 -0
- package/dist/core/ingestion/languages/typescript/arity.d.ts +37 -0
- package/dist/core/ingestion/languages/typescript/arity.js +54 -0
- package/dist/core/ingestion/languages/typescript/cache-stats.d.ts +17 -0
- package/dist/core/ingestion/languages/typescript/cache-stats.js +28 -0
- package/dist/core/ingestion/languages/typescript/captures.d.ts +28 -0
- package/dist/core/ingestion/languages/typescript/captures.js +451 -0
- package/dist/core/ingestion/languages/typescript/import-decomposer.d.ts +49 -0
- package/dist/core/ingestion/languages/typescript/import-decomposer.js +371 -0
- package/dist/core/ingestion/languages/typescript/import-target.d.ts +50 -0
- package/dist/core/ingestion/languages/typescript/import-target.js +61 -0
- package/dist/core/ingestion/languages/typescript/index.d.ts +94 -0
- package/dist/core/ingestion/languages/typescript/index.js +94 -0
- package/dist/core/ingestion/languages/typescript/interpret.d.ts +35 -0
- package/dist/core/ingestion/languages/typescript/interpret.js +317 -0
- package/dist/core/ingestion/languages/typescript/merge-bindings.d.ts +62 -0
- package/dist/core/ingestion/languages/typescript/merge-bindings.js +158 -0
- package/dist/core/ingestion/languages/typescript/query.d.ts +77 -0
- package/dist/core/ingestion/languages/typescript/query.js +778 -0
- package/dist/core/ingestion/languages/typescript/receiver-binding.d.ts +59 -0
- package/dist/core/ingestion/languages/typescript/receiver-binding.js +171 -0
- package/dist/core/ingestion/languages/typescript/scope-resolver.d.ts +16 -0
- package/dist/core/ingestion/languages/typescript/scope-resolver.js +113 -0
- package/dist/core/ingestion/languages/typescript/simple-hooks.d.ts +71 -0
- package/dist/core/ingestion/languages/typescript/simple-hooks.js +131 -0
- package/dist/core/ingestion/languages/typescript.js +19 -0
- package/dist/core/ingestion/method-extractors/configs/swift.js +3 -4
- package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +14 -1
- package/dist/core/ingestion/parsing-processor.js +20 -9
- package/dist/core/ingestion/pipeline-phases/processes.js +9 -4
- package/dist/core/ingestion/pipeline-phases/tools.d.ts +1 -0
- package/dist/core/ingestion/pipeline-phases/tools.js +10 -4
- package/dist/core/ingestion/registry-primary-flag.d.ts +3 -1
- package/dist/core/ingestion/registry-primary-flag.js +4 -1
- package/dist/core/ingestion/scope-extractor-bridge.d.ts +5 -2
- package/dist/core/ingestion/scope-extractor-bridge.js +7 -2
- package/dist/core/ingestion/scope-extractor.js +19 -18
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +73 -11
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +48 -10
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +283 -14
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +23 -2
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +109 -37
- package/dist/core/ingestion/scope-resolution/passes/mro.js +3 -1
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +13 -5
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +11 -2
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +8 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +21 -5
- package/dist/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.d.ts +39 -0
- package/dist/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.js +65 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +54 -11
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +105 -30
- package/dist/core/ingestion/type-extractors/swift.js +7 -4
- package/dist/core/ingestion/utils/ast-helpers.d.ts +2 -0
- package/dist/core/ingestion/utils/ast-helpers.js +12 -0
- package/dist/core/ingestion/utils/env.d.ts +10 -0
- package/dist/core/ingestion/utils/env.js +14 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +1 -0
- package/dist/core/ingestion/workers/parse-worker.js +15 -9
- package/dist/core/ingestion/workers/worker-pool.d.ts +11 -4
- package/dist/core/ingestion/workers/worker-pool.js +244 -48
- package/dist/core/lbug/extension-loader.d.ts +86 -0
- package/dist/core/lbug/extension-loader.js +184 -0
- package/dist/core/lbug/lbug-adapter.d.ts +18 -17
- package/dist/core/lbug/lbug-adapter.js +45 -73
- package/dist/core/lbug/pool-adapter.js +10 -28
- package/dist/core/platform/capabilities.d.ts +24 -0
- package/dist/core/platform/capabilities.js +54 -0
- package/dist/core/run-analyze.js +36 -9
- package/dist/core/search/bm25-index.d.ts +0 -17
- package/dist/core/search/bm25-index.js +10 -118
- package/dist/core/search/fts-indexes.d.ts +1 -0
- package/dist/core/search/fts-indexes.js +7 -0
- package/dist/core/search/fts-schema.d.ts +6 -0
- package/dist/core/search/fts-schema.js +7 -0
- package/dist/mcp/core/embedder.js +11 -4
- package/dist/mcp/local/local-backend.js +50 -15
- package/dist/server/api.d.ts +5 -0
- package/dist/server/api.js +113 -0
- package/hooks/claude/gitnexus-hook.cjs +11 -1
- package/package.json +6 -5
- package/scripts/build-tree-sitter-dart.cjs +42 -0
- package/scripts/build-tree-sitter-proto.cjs +1 -1
- package/scripts/build.js +22 -2
- package/scripts/install-duckdb-extension.mjs +37 -0
- package/vendor/tree-sitter-dart/README.md +18 -0
- package/vendor/tree-sitter-dart/binding.gyp +31 -0
- package/vendor/tree-sitter-dart/bindings/node/binding.cc +20 -0
- package/vendor/tree-sitter-dart/bindings/node/index.d.ts +28 -0
- package/vendor/tree-sitter-dart/bindings/node/index.js +7 -0
- package/vendor/tree-sitter-dart/grammar.js +2895 -0
- package/vendor/tree-sitter-dart/package.json +18 -0
- package/vendor/tree-sitter-dart/queries/highlights.scm +246 -0
- package/vendor/tree-sitter-dart/queries/tags.scm +92 -0
- package/vendor/tree-sitter-dart/queries/test.scm +1 -0
- package/vendor/tree-sitter-dart/src/grammar.json +12459 -0
- package/vendor/tree-sitter-dart/src/node-types.json +15055 -0
- package/vendor/tree-sitter-dart/src/parser.c +196127 -0
- package/vendor/tree-sitter-dart/src/scanner.c +130 -0
- package/vendor/tree-sitter-dart/src/tree_sitter/alloc.h +54 -0
- package/vendor/tree-sitter-dart/src/tree_sitter/array.h +290 -0
- package/vendor/tree-sitter-dart/src/tree_sitter/parser.h +265 -0
- package/vendor/tree-sitter-swift/LICENSE +21 -0
- package/vendor/tree-sitter-swift/README.md +139 -0
- package/vendor/tree-sitter-swift/bindings/node/index.d.ts +28 -0
- package/vendor/tree-sitter-swift/bindings/node/index.js +7 -0
- package/vendor/tree-sitter-swift/package.json +28 -0
- package/vendor/tree-sitter-swift/prebuilds/darwin-arm64/tree-sitter-swift.node +0 -0
- package/vendor/tree-sitter-swift/prebuilds/darwin-x64/tree-sitter-swift.node +0 -0
- package/vendor/tree-sitter-swift/prebuilds/linux-arm64/tree-sitter-swift.node +0 -0
- package/vendor/tree-sitter-swift/prebuilds/linux-x64/tree-sitter-swift.node +0 -0
- package/vendor/tree-sitter-swift/prebuilds/win32-arm64/tree-sitter-swift.node +0 -0
- package/vendor/tree-sitter-swift/prebuilds/win32-x64/tree-sitter-swift.node +0 -0
- package/vendor/tree-sitter-swift/src/node-types.json +30694 -0
- package/web/assets/agent-DaprsFSX.js +597 -0
- package/web/assets/architecture-YZFGNWBL-S5CXDPWN-DEdGaPg2.js +1 -0
- package/web/assets/architectureDiagram-EMZXCZ2Q-Domyk_gO.js +36 -0
- package/web/assets/blockDiagram-IGV67L2C-B_2kD7tM.js +132 -0
- package/web/assets/c4Diagram-DFAF54RM-BhJJW8Gg.js +10 -0
- package/web/assets/chunk-3GS5O3IE-jlWIjPsl.js +231 -0
- package/web/assets/chunk-3YCYZ6SJ-Blq_IzZs.js +1 -0
- package/web/assets/chunk-6NTNNK5N-DyPc58pp.js +1 -0
- package/web/assets/chunk-7RZVMHOQ-BdIU-RGO.js +321 -0
- package/web/assets/chunk-A34GCYZU-BI2i_LdU.js +1 -0
- package/web/assets/chunk-AEOMTBSW-D7qjBMHW.js +1 -0
- package/web/assets/chunk-CilyBKbf.js +1 -0
- package/web/assets/chunk-DJ7UZH7F-i11ywiBl.js +1 -0
- package/web/assets/chunk-DKKBVRCY-1SffGI1N.js +4 -0
- package/web/assets/chunk-DU5LTGQ6-DaPeiwD5.js +1 -0
- package/web/assets/chunk-FXACKDTF-uhhi2PC2.js +159 -0
- package/web/assets/chunk-H3VCZNTA-IchcISDt.js +1 -0
- package/web/assets/chunk-HN6EAY2L-D7ZFMNrB.js +1 -0
- package/web/assets/chunk-KSICW3F5-C2tZmXwv.js +15 -0
- package/web/assets/chunk-O5ABG6QK-Bt-Km84H.js +1 -0
- package/web/assets/chunk-PK6DOVAG-ChlWY0BQ.js +206 -0
- package/web/assets/chunk-RNJOYNJ4-B724K7cW.js +1 -0
- package/web/assets/chunk-RWUO3TPN-DYn1XriD.js +1 -0
- package/web/assets/chunk-TBF5ZNIQ-DKtDz6ae.js +1 -0
- package/web/assets/chunk-TU3PZOEN-DE5Qhc0N.js +1 -0
- package/web/assets/chunk-TYMNRAUI-g1h33cq-.js +1 -0
- package/web/assets/chunk-VELTKBKT-C9dVN39o.js +1 -0
- package/web/assets/chunk-W7ZLLLMY-Du-Hb9yb.js +1 -0
- package/web/assets/chunk-WSB5WSVC-B123clsZ.js +1 -0
- package/web/assets/chunk-XGPFEOL4-BR7Eue38.js +1 -0
- package/web/assets/classDiagram-PPOCWD7C-BglfKSs_.js +1 -0
- package/web/assets/classDiagram-v2-23LJLIIU-BSzTM28O.js +1 -0
- package/web/assets/context-builder-CqQNhRj1.js +15 -0
- package/web/assets/cose-bilkent-PNC4W37J-DCfErU-A.js +1 -0
- package/web/assets/dagre-E77IOHMT-tDRRhDoN.js +4 -0
- package/web/assets/diagram-H7BISOXX-CUVHlmAh.js +43 -0
- package/web/assets/diagram-JC5VWROH-BoyOxulB.js +24 -0
- package/web/assets/diagram-LXUTUG65-osr9hb7N.js +10 -0
- package/web/assets/diagram-WEHSV5V5-d8nUqS39.js +24 -0
- package/web/assets/erDiagram-GCSMX5X6-b-IwOhPS.js +85 -0
- package/web/assets/flowDiagram-OTCZ4VVT-Ott2Q0AP.js +162 -0
- package/web/assets/ganttDiagram-MUNLMDZQ-BYtgN_5s.js +292 -0
- package/web/assets/gitGraph-7Q5UKJZL-54BCDZD5-CFyBIGZq.js +1 -0
- package/web/assets/gitGraphDiagram-3HKGZ4G3-CsVD2gn4.js +106 -0
- package/web/assets/index-BleGLU8S.css +2 -0
- package/web/assets/index-C_xK08EW.js +885 -0
- package/web/assets/info-OMHHGYJF-BF2H5H6G-yjAxKEzh.js +1 -0
- package/web/assets/infoDiagram-MN7RKWGX-DXK0Unn5.js +2 -0
- package/web/assets/ishikawaDiagram-YMYX4NHK-CXsnC2FA.js +70 -0
- package/web/assets/journeyDiagram-SO5T7YLQ-BzZ07B-X.js +139 -0
- package/web/assets/kanban-definition-LJHFXRCJ-C6_EpAd9.js +89 -0
- package/web/assets/katex-GD7MH7QM-CJiOjBBJ.js +261 -0
- package/web/assets/mindmap-definition-2EUWGEK5-CCYGWZ1m.js +96 -0
- package/web/assets/packet-4T2RLAQJ-EV4IVRXR-B8k4E3IT.js +1 -0
- package/web/assets/pie-ZZUOXDRM-N23DN5KN-DdvfY118.js +1 -0
- package/web/assets/pieDiagram-3IATQBI2-RyvRlQb4.js +30 -0
- package/web/assets/quadrantDiagram-E256RVCF-Bfb6sxCx.js +7 -0
- package/web/assets/radar-PYXPWWZC-P6TP7ZYP-1EEDC_yU.js +1 -0
- package/web/assets/requirementDiagram-M5DCFWZL-DjvHDyvN.js +84 -0
- package/web/assets/sankeyDiagram-L3NBLAOT-CBCbbl8s.js +10 -0
- package/web/assets/sequenceDiagram-ZOUHS735-BscU8TUR.js +157 -0
- package/web/assets/stateDiagram-MLPALWAM-CJusEK2D.js +1 -0
- package/web/assets/stateDiagram-v2-B5LQ5ZB2-DImJ3PXD.js +1 -0
- package/web/assets/timeline-definition-5SPVSISX-DigPA1X8.js +120 -0
- package/web/assets/treeView-SZITEDCU-5DXDK3XO-CzPDt3aG.js +1 -0
- package/web/assets/treemap-W4RFUUIX-WYLRDWKO-B9Iqiorr.js +1 -0
- package/web/assets/vennDiagram-IE5QUKF5-C91UkZIf.js +34 -0
- package/web/assets/wardley-RL74JXVD-BCRCBASE-x42Qw7hp.js +1 -0
- package/web/assets/wardleyDiagram-XU3VSMPF-DloBhI0U.js +20 -0
- package/web/assets/xychartDiagram-ZHJ5623Y-BGWJvgwI.js +7 -0
- package/web/index.html +21 -0
- package/scripts/patch-tree-sitter-swift.cjs +0 -78
package/dist/core/run-analyze.js
CHANGED
|
@@ -12,6 +12,7 @@ import path from 'path';
|
|
|
12
12
|
import fs from 'fs/promises';
|
|
13
13
|
import { runPipelineFromRepo } from './ingestion/pipeline.js';
|
|
14
14
|
import { initLbug, loadGraphToLbug, getLbugStats, executeQuery, executeWithReusedStatement, closeLbug, loadCachedEmbeddings, } from './lbug/lbug-adapter.js';
|
|
15
|
+
import { createSearchFTSIndexes } from './search/fts-indexes.js';
|
|
15
16
|
import { getStoragePaths, saveMeta, loadMeta, addToGitignore, registerRepo, cleanupOldKuzuFiles, } from '../storage/repo-manager.js';
|
|
16
17
|
import { getCurrentCommit, getRemoteUrl, hasGitDir, getInferredRepoName } from '../storage/git.js';
|
|
17
18
|
import { generateAIContextFiles } from '../cli/ai-context.js';
|
|
@@ -139,7 +140,8 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
139
140
|
const pipelineResult = await runPipelineFromRepo(repoPath, (p) => {
|
|
140
141
|
const phaseLabel = PHASE_LABELS[p.phase] || p.phase;
|
|
141
142
|
const scaled = Math.round(p.percent * 0.6);
|
|
142
|
-
|
|
143
|
+
const message = p.detail ? `${p.message || phaseLabel} (${p.detail})` : p.message || phaseLabel;
|
|
144
|
+
progress(p.phase, scaled, message);
|
|
143
145
|
});
|
|
144
146
|
// ── Phase 2: LadybugDB (60–85%) ──────────────────────────────────
|
|
145
147
|
progress('lbug', 60, 'Loading into LadybugDB...');
|
|
@@ -165,12 +167,9 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
165
167
|
progress('lbug', pct, msg);
|
|
166
168
|
});
|
|
167
169
|
// ── Phase 3: FTS (85–90%) ─────────────────────────────────────────
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
// which dominated `analyze` runtime and pushed Windows CI past its
|
|
172
|
-
// 30 s test budget. Lazy creation is implemented in
|
|
173
|
-
// `core/search/bm25-index.ts` via `ensureFTSIndex`.
|
|
170
|
+
progress('fts', 85, 'Creating search indexes...');
|
|
171
|
+
await createSearchFTSIndexes();
|
|
172
|
+
progress('fts', 90, 'Search indexes ready');
|
|
174
173
|
// ── Phase 3.5: Re-insert cached embeddings ────────────────────────
|
|
175
174
|
if (cachedEmbeddings.length > 0) {
|
|
176
175
|
const cachedDims = cachedEmbeddings[0].embedding.length;
|
|
@@ -199,6 +198,7 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
199
198
|
// ── Phase 4: Embeddings (90–98%) ──────────────────────────────────
|
|
200
199
|
const stats = await getLbugStats();
|
|
201
200
|
let embeddingSkipped = true;
|
|
201
|
+
let semanticMode;
|
|
202
202
|
if (shouldGenerateEmbeddings) {
|
|
203
203
|
if (stats.nodes <= EMBEDDING_NODE_LIMIT) {
|
|
204
204
|
embeddingSkipped = false;
|
|
@@ -220,7 +220,7 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
220
220
|
const { readServerMapping } = await import('./embeddings/server-mapping.js');
|
|
221
221
|
const projectName = path.basename(repoPath);
|
|
222
222
|
const serverName = await readServerMapping(projectName);
|
|
223
|
-
await runEmbeddingPipeline(executeQuery, executeWithReusedStatement, (p) => {
|
|
223
|
+
const embeddingResult = await runEmbeddingPipeline(executeQuery, executeWithReusedStatement, (p) => {
|
|
224
224
|
const scaled = 90 + Math.round((p.percent / 100) * 8);
|
|
225
225
|
const label = p.phase === 'loading-model'
|
|
226
226
|
? httpMode
|
|
@@ -229,6 +229,14 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
229
229
|
: `Embedding ${p.nodesProcessed || 0}/${p.totalNodes || '?'}`;
|
|
230
230
|
progress('embeddings', scaled, label);
|
|
231
231
|
}, {}, cachedEmbeddingNodeIds.size > 0 ? cachedEmbeddingNodeIds : undefined, { repoName: projectName, serverName }, existingEmbeddings);
|
|
232
|
+
if (embeddingResult.semanticMode === 'exact-scan') {
|
|
233
|
+
semanticMode = 'exact-scan';
|
|
234
|
+
log('Semantic embeddings were generated without a VECTOR index; ' +
|
|
235
|
+
'queries will use exact-scan fallback within the configured limit.');
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
semanticMode = 'vector-index';
|
|
239
|
+
}
|
|
232
240
|
}
|
|
233
241
|
// ── Phase 5: Finalize (98–100%) ───────────────────────────────────
|
|
234
242
|
progress('done', 98, 'Saving metadata...');
|
|
@@ -236,11 +244,20 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
236
244
|
let embeddingCount = 0;
|
|
237
245
|
try {
|
|
238
246
|
const embResult = await executeQuery(`MATCH (e:${EMBEDDING_TABLE_NAME}) RETURN count(e) AS cnt`);
|
|
239
|
-
|
|
247
|
+
const row = embResult?.[0];
|
|
248
|
+
embeddingCount = Number(row?.cnt ?? row?.[0] ?? 0);
|
|
240
249
|
}
|
|
241
250
|
catch {
|
|
242
251
|
/* table may not exist if embeddings never ran */
|
|
243
252
|
}
|
|
253
|
+
if (!embeddingSkipped && stats.nodes > 0 && embeddingCount === 0) {
|
|
254
|
+
throw new Error('Embedding generation completed without persisted embeddings. ' +
|
|
255
|
+
'The index was not registered to avoid silently reporting embeddings: 0.');
|
|
256
|
+
}
|
|
257
|
+
const { getRuntimeCapabilities } = await import('./platform/capabilities.js');
|
|
258
|
+
const runtimeCapabilities = getRuntimeCapabilities();
|
|
259
|
+
const effectiveSemanticMode = semanticMode ??
|
|
260
|
+
(runtimeCapabilities.semanticMode === 'vector-index' ? 'vector-index' : 'exact-scan');
|
|
244
261
|
const meta = {
|
|
245
262
|
repoPath,
|
|
246
263
|
lastCommit: currentCommit,
|
|
@@ -260,6 +277,16 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
260
277
|
processes: pipelineResult.processResult?.stats.totalProcesses,
|
|
261
278
|
embeddings: embeddingCount,
|
|
262
279
|
},
|
|
280
|
+
capabilities: {
|
|
281
|
+
graph: { provider: 'ladybugdb', status: runtimeCapabilities.graph },
|
|
282
|
+
fts: { provider: 'ladybugdb-fts', status: runtimeCapabilities.fts },
|
|
283
|
+
vectorSearch: {
|
|
284
|
+
provider: effectiveSemanticMode === 'vector-index' ? 'ladybugdb-vector' : 'exact-scan',
|
|
285
|
+
status: embeddingCount > 0 ? effectiveSemanticMode : 'unavailable',
|
|
286
|
+
exactScanLimit: runtimeCapabilities.exactScanLimit,
|
|
287
|
+
reason: runtimeCapabilities.reason,
|
|
288
|
+
},
|
|
289
|
+
},
|
|
263
290
|
};
|
|
264
291
|
await saveMeta(storagePath, meta);
|
|
265
292
|
// Forward the --name alias and the registry-collision bypass bit.
|
|
@@ -3,12 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Uses LadybugDB's built-in full-text search indexes for keyword-based search.
|
|
5
5
|
* Always reads from the database (no cached state to drift).
|
|
6
|
-
*
|
|
7
|
-
* FTS indexes are created lazily on first query (via `ensureFTSIndex`) — see
|
|
8
|
-
* `lbug-adapter.ts` for the rationale. This keeps `analyze` fast (the
|
|
9
|
-
* ~440 ms × 5 LadybugDB CREATE_FTS_INDEX cost dominates pipeline time on
|
|
10
|
-
* small repos / CI runners) at the cost of paying that overhead on the
|
|
11
|
-
* first `query`/`context` call in a session.
|
|
12
6
|
*/
|
|
13
7
|
export interface BM25SearchResult {
|
|
14
8
|
filePath: string;
|
|
@@ -16,17 +10,6 @@ export interface BM25SearchResult {
|
|
|
16
10
|
rank: number;
|
|
17
11
|
nodeIds?: string[];
|
|
18
12
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Drop all ensured-FTS cache entries for a given repoId.
|
|
21
|
-
*
|
|
22
|
-
* Called from the pool-close listener so that a pool teardown / recreation
|
|
23
|
-
* forces the next `searchFTSFromLbug` call to re-issue `CREATE_FTS_INDEX`
|
|
24
|
-
* against the fresh connection rather than trust stale ensure-state from a
|
|
25
|
-
* previous pool lifetime.
|
|
26
|
-
*
|
|
27
|
-
* Exported for tests; the listener wiring is internal.
|
|
28
|
-
*/
|
|
29
|
-
export declare function invalidateEnsuredFTSForRepo(repoId: string): void;
|
|
30
13
|
/**
|
|
31
14
|
* Search using LadybugDB's built-in FTS (always fresh, reads from disk)
|
|
32
15
|
*
|
|
@@ -3,96 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Uses LadybugDB's built-in full-text search indexes for keyword-based search.
|
|
5
5
|
* Always reads from the database (no cached state to drift).
|
|
6
|
-
*
|
|
7
|
-
* FTS indexes are created lazily on first query (via `ensureFTSIndex`) — see
|
|
8
|
-
* `lbug-adapter.ts` for the rationale. This keeps `analyze` fast (the
|
|
9
|
-
* ~440 ms × 5 LadybugDB CREATE_FTS_INDEX cost dominates pipeline time on
|
|
10
|
-
* small repos / CI runners) at the cost of paying that overhead on the
|
|
11
|
-
* first `query`/`context` call in a session.
|
|
12
|
-
*/
|
|
13
|
-
import { queryFTS, ensureFTSIndex } from '../lbug/lbug-adapter.js';
|
|
14
|
-
/**
|
|
15
|
-
* FTS schema served by `searchFTSFromLbug`. Centralised so that both the
|
|
16
|
-
* CLI/pipeline path and the MCP pool path use identical (table, index,
|
|
17
|
-
* properties) tuples and the lazy-create logic stays in one place.
|
|
18
|
-
*/
|
|
19
|
-
const FTS_INDEXES = [
|
|
20
|
-
{ table: 'File', indexName: 'file_fts', properties: ['name', 'content'] },
|
|
21
|
-
{ table: 'Function', indexName: 'function_fts', properties: ['name', 'content'] },
|
|
22
|
-
{ table: 'Class', indexName: 'class_fts', properties: ['name', 'content'] },
|
|
23
|
-
{ table: 'Method', indexName: 'method_fts', properties: ['name', 'content'] },
|
|
24
|
-
{ table: 'Interface', indexName: 'interface_fts', properties: ['name', 'content'] },
|
|
25
|
-
];
|
|
26
|
-
/**
|
|
27
|
-
* Per-process cache for the MCP pool path: tracks which `(repoId, table)`
|
|
28
|
-
* pairs have been ensured. The CLI/pipeline path gets its own cache inside
|
|
29
|
-
* `lbug-adapter.ts` keyed by table/index, scoped to the singleton connection.
|
|
30
|
-
*
|
|
31
|
-
* IMPORTANT: an entry is added ONLY when the index was confirmed to exist
|
|
32
|
-
* (CREATE_FTS_INDEX succeeded, or failed with `'already exists'`). Other
|
|
33
|
-
* failures (transient lock errors, missing extension, etc.) leave the key
|
|
34
|
-
* unset so the next query retries instead of silently caching the failure.
|
|
35
|
-
*
|
|
36
|
-
* Entries for a given repoId are invalidated when its pool is closed —
|
|
37
|
-
* see the `addPoolCloseListener` registration in `searchFTSFromLbug`.
|
|
38
|
-
*/
|
|
39
|
-
const ensuredPoolFTS = new Set();
|
|
40
|
-
/**
|
|
41
|
-
* Drop all ensured-FTS cache entries for a given repoId.
|
|
42
|
-
*
|
|
43
|
-
* Called from the pool-close listener so that a pool teardown / recreation
|
|
44
|
-
* forces the next `searchFTSFromLbug` call to re-issue `CREATE_FTS_INDEX`
|
|
45
|
-
* against the fresh connection rather than trust stale ensure-state from a
|
|
46
|
-
* previous pool lifetime.
|
|
47
|
-
*
|
|
48
|
-
* Exported for tests; the listener wiring is internal.
|
|
49
6
|
*/
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
for (const key of ensuredPoolFTS) {
|
|
53
|
-
if (key.startsWith(prefix))
|
|
54
|
-
ensuredPoolFTS.delete(key);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Tracks whether we've already wired the pool-close listener for this
|
|
59
|
-
* process. The pool adapter is dynamically imported, so registration
|
|
60
|
-
* happens lazily on the first MCP-pool-backed FTS query.
|
|
61
|
-
*/
|
|
62
|
-
let poolCloseListenerRegistered = false;
|
|
63
|
-
function registerPoolCloseListenerOnce(addPoolCloseListener) {
|
|
64
|
-
if (poolCloseListenerRegistered)
|
|
65
|
-
return;
|
|
66
|
-
poolCloseListenerRegistered = true;
|
|
67
|
-
addPoolCloseListener((repoId) => invalidateEnsuredFTSForRepo(repoId));
|
|
68
|
-
}
|
|
69
|
-
async function ensureFTSIndexViaExecutor(executor, repoId, table, indexName, properties) {
|
|
70
|
-
const key = `${repoId}:${table}:${indexName}`;
|
|
71
|
-
if (ensuredPoolFTS.has(key))
|
|
72
|
-
return;
|
|
73
|
-
const propList = properties.map((p) => `'${p}'`).join(', ');
|
|
74
|
-
try {
|
|
75
|
-
await executor(`CALL CREATE_FTS_INDEX('${table}', '${indexName}', [${propList}], stemmer := 'porter')`);
|
|
76
|
-
// Index was created successfully — safe to cache.
|
|
77
|
-
ensuredPoolFTS.add(key);
|
|
78
|
-
}
|
|
79
|
-
catch (e) {
|
|
80
|
-
// 'already exists' is the happy path (index persists on disk between
|
|
81
|
-
// process invocations) — cache it. Anything else is treated as a
|
|
82
|
-
// transient failure: surface a one-time warning and leave the key
|
|
83
|
-
// unset so the NEXT query retries rather than silently using a
|
|
84
|
-
// cached failure (which previously disabled BM25 for the whole
|
|
85
|
-
// process for that repo).
|
|
86
|
-
const msg = String(e?.message ?? '');
|
|
87
|
-
if (msg.includes('already exists')) {
|
|
88
|
-
ensuredPoolFTS.add(key);
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
console.warn(`[gitnexus] FTS index ensure failed for repo "${repoId}" table "${table}" ` +
|
|
92
|
-
`(index "${indexName}"): ${msg || e}. Will retry on next query.`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
7
|
+
import { queryFTS } from '../lbug/lbug-adapter.js';
|
|
8
|
+
import { FTS_INDEXES } from './fts-schema.js';
|
|
96
9
|
/**
|
|
97
10
|
* Execute a single FTS query via a custom executor (for MCP connection pool).
|
|
98
11
|
* Returns the same shape as core queryFTS (from LadybugDB adapter).
|
|
@@ -134,41 +47,23 @@ async function queryFTSViaExecutor(executor, tableName, indexName, query, limit)
|
|
|
134
47
|
* @returns Ranked search results from FTS indexes
|
|
135
48
|
*/
|
|
136
49
|
export const searchFTSFromLbug = async (query, limit = 20, repoId) => {
|
|
137
|
-
|
|
50
|
+
const resultsByIndex = [];
|
|
138
51
|
if (repoId) {
|
|
139
52
|
// Use MCP connection pool via dynamic import
|
|
140
53
|
// IMPORTANT: FTS queries run sequentially to avoid connection contention.
|
|
141
54
|
// The MCP pool supports multiple connections, but FTS is best run serially.
|
|
142
55
|
const poolMod = await import('../lbug/pool-adapter.js');
|
|
143
|
-
const { executeQuery
|
|
144
|
-
// Register the pool-close listener lazily on first use so a teardown of
|
|
145
|
-
// the pool entry (LRU eviction, idle timeout, explicit close) drops the
|
|
146
|
-
// matching `ensuredPoolFTS` entries. Without this, stale ensure-state
|
|
147
|
-
// can outlive the pool that produced it.
|
|
148
|
-
registerPoolCloseListenerOnce(addPoolCloseListener);
|
|
56
|
+
const { executeQuery } = poolMod;
|
|
149
57
|
const executor = (cypher) => executeQuery(repoId, cypher);
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
for (const { table, indexName, properties } of FTS_INDEXES) {
|
|
153
|
-
await ensureFTSIndexViaExecutor(executor, repoId, table, indexName, properties);
|
|
58
|
+
for (const { table, indexName } of FTS_INDEXES) {
|
|
59
|
+
resultsByIndex.push(await queryFTSViaExecutor(executor, table, indexName, query, limit));
|
|
154
60
|
}
|
|
155
|
-
fileResults = await queryFTSViaExecutor(executor, 'File', 'file_fts', query, limit);
|
|
156
|
-
functionResults = await queryFTSViaExecutor(executor, 'Function', 'function_fts', query, limit);
|
|
157
|
-
classResults = await queryFTSViaExecutor(executor, 'Class', 'class_fts', query, limit);
|
|
158
|
-
methodResults = await queryFTSViaExecutor(executor, 'Method', 'method_fts', query, limit);
|
|
159
|
-
interfaceResults = await queryFTSViaExecutor(executor, 'Interface', 'interface_fts', query, limit);
|
|
160
61
|
}
|
|
161
62
|
else {
|
|
162
63
|
// Use core lbug adapter (CLI / pipeline context) — also sequential for safety.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
await ensureFTSIndex(table, indexName, [...properties]).catch(() => { });
|
|
64
|
+
for (const { table, indexName } of FTS_INDEXES) {
|
|
65
|
+
resultsByIndex.push(await queryFTS(table, indexName, query, limit, false).catch(() => []));
|
|
166
66
|
}
|
|
167
|
-
fileResults = await queryFTS('File', 'file_fts', query, limit, false).catch(() => []);
|
|
168
|
-
functionResults = await queryFTS('Function', 'function_fts', query, limit, false).catch(() => []);
|
|
169
|
-
classResults = await queryFTS('Class', 'class_fts', query, limit, false).catch(() => []);
|
|
170
|
-
methodResults = await queryFTS('Method', 'method_fts', query, limit, false).catch(() => []);
|
|
171
|
-
interfaceResults = await queryFTS('Interface', 'interface_fts', query, limit, false).catch(() => []);
|
|
172
67
|
}
|
|
173
68
|
// Collect all node scores per filePath to track which nodes actually matched
|
|
174
69
|
const fileNodeScores = new Map();
|
|
@@ -179,11 +74,8 @@ export const searchFTSFromLbug = async (query, limit = 20, repoId) => {
|
|
|
179
74
|
fileNodeScores.get(r.filePath).push({ score: r.score, nodeId: r.nodeId });
|
|
180
75
|
}
|
|
181
76
|
};
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
addResults(classResults);
|
|
185
|
-
addResults(methodResults);
|
|
186
|
-
addResults(interfaceResults);
|
|
77
|
+
for (const results of resultsByIndex)
|
|
78
|
+
addResults(results);
|
|
187
79
|
// Sum the top-3 highest-scoring nodes per file and collect their nodeIds.
|
|
188
80
|
// Summing all nodes naively inflates scores for files with many mediocre
|
|
189
81
|
// matches (e.g. test files) over files with a single highly-relevant symbol.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createSearchFTSIndexes(): Promise<void>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createFTSIndex } from '../lbug/lbug-adapter.js';
|
|
2
|
+
import { FTS_INDEXES } from './fts-schema.js';
|
|
3
|
+
export async function createSearchFTSIndexes() {
|
|
4
|
+
for (const { table, indexName, properties } of FTS_INDEXES) {
|
|
5
|
+
await createFTSIndex(table, indexName, [...properties]);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const FTS_INDEXES = [
|
|
2
|
+
{ table: 'File', indexName: 'file_fts', properties: ['name', 'content'] },
|
|
3
|
+
{ table: 'Function', indexName: 'function_fts', properties: ['name', 'content'] },
|
|
4
|
+
{ table: 'Class', indexName: 'class_fts', properties: ['name', 'content'] },
|
|
5
|
+
{ table: 'Method', indexName: 'method_fts', properties: ['name', 'content'] },
|
|
6
|
+
{ table: 'Interface', indexName: 'interface_fts', properties: ['name', 'content'] },
|
|
7
|
+
];
|
|
@@ -8,6 +8,7 @@ import { pipeline, env } from '@huggingface/transformers';
|
|
|
8
8
|
import os from 'os';
|
|
9
9
|
import { join } from 'path';
|
|
10
10
|
import { isHttpMode, getHttpDimensions, httpEmbedQuery, } from '../../core/embeddings/http-client.js';
|
|
11
|
+
import { resolveEmbeddingConfig } from '../../core/embeddings/config.js';
|
|
11
12
|
import { silenceStdout, restoreStdout, realStderrWrite } from '../../core/lbug/pool-adapter.js';
|
|
12
13
|
// Model config
|
|
13
14
|
const MODEL_ID = 'Snowflake/snowflake-arctic-embed-xs';
|
|
@@ -37,11 +38,11 @@ export const initEmbedder = async () => {
|
|
|
37
38
|
// when gitnexus is installed globally (e.g. /usr/lib/node_modules/).
|
|
38
39
|
// Respect HF_HOME if set, otherwise fall back to ~/.cache/huggingface.
|
|
39
40
|
env.cacheDir = process.env.HF_HOME ?? join(os.homedir(), '.cache', 'huggingface');
|
|
41
|
+
const embeddingConfig = resolveEmbeddingConfig();
|
|
40
42
|
console.error('GitNexus: Loading embedding model (first search may take a moment)...');
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const devicesToTry = [gpuDevice, 'cpu'];
|
|
43
|
+
const devicesToTry = embeddingConfig.device === 'dml' || embeddingConfig.device === 'cuda'
|
|
44
|
+
? [embeddingConfig.device, 'cpu']
|
|
45
|
+
: ['cpu'];
|
|
45
46
|
for (const device of devicesToTry) {
|
|
46
47
|
try {
|
|
47
48
|
// Silence stdout and stderr during model load — ONNX Runtime and transformers.js
|
|
@@ -55,6 +56,12 @@ export const initEmbedder = async () => {
|
|
|
55
56
|
embedderInstance = await pipeline('feature-extraction', MODEL_ID, {
|
|
56
57
|
device: device,
|
|
57
58
|
dtype: 'fp32',
|
|
59
|
+
session_options: {
|
|
60
|
+
logSeverityLevel: 3,
|
|
61
|
+
intraOpNumThreads: embeddingConfig.threads,
|
|
62
|
+
interOpNumThreads: 1,
|
|
63
|
+
executionMode: 'sequential',
|
|
64
|
+
},
|
|
58
65
|
});
|
|
59
66
|
}
|
|
60
67
|
finally {
|
|
@@ -18,7 +18,9 @@ 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 { rankExactEmbeddingRows, } from '../../core/embeddings/exact-search.js';
|
|
21
22
|
import { EMBEDDING_TABLE_NAME, EMBEDDING_INDEX_NAME } from '../../core/lbug/schema.js';
|
|
23
|
+
import { getExactScanLimit } from '../../core/platform/capabilities.js';
|
|
22
24
|
import { PhaseTimer } from '../../core/search/phase-timer.js';
|
|
23
25
|
import { checkStaleness, checkCwdMatch } from '../../core/git-staleness.js';
|
|
24
26
|
// AI context generation is CLI-only (gitnexus analyze)
|
|
@@ -889,26 +891,59 @@ export class LocalBackend {
|
|
|
889
891
|
const queryVec = await embedQuery(query);
|
|
890
892
|
const dims = getEmbeddingDims();
|
|
891
893
|
const queryVecStr = `[${queryVec.join(',')}]`;
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
894
|
+
let bestChunks = new Map();
|
|
895
|
+
try {
|
|
896
|
+
bestChunks = await collectBestChunks(limit, async (fetchLimit) => {
|
|
897
|
+
const vectorQuery = `
|
|
898
|
+
CALL QUERY_VECTOR_INDEX('${EMBEDDING_TABLE_NAME}', '${EMBEDDING_INDEX_NAME}',
|
|
899
|
+
CAST(${queryVecStr} AS FLOAT[${dims}]), ${fetchLimit})
|
|
900
|
+
YIELD node AS emb, distance
|
|
901
|
+
WITH emb, distance
|
|
902
|
+
WHERE distance < 0.6
|
|
903
|
+
RETURN emb.nodeId AS nodeId, emb.chunkIndex AS chunkIndex,
|
|
904
|
+
emb.startLine AS startLine, emb.endLine AS endLine, distance
|
|
905
|
+
ORDER BY distance
|
|
906
|
+
`;
|
|
907
|
+
const embResults = await executeQuery(repo.id, vectorQuery);
|
|
908
|
+
return embResults.map((row) => ({
|
|
909
|
+
nodeId: row.nodeId ?? row[0],
|
|
910
|
+
chunkIndex: row.chunkIndex ?? row[1] ?? 0,
|
|
911
|
+
startLine: row.startLine ?? row[2] ?? 0,
|
|
912
|
+
endLine: row.endLine ?? row[3] ?? 0,
|
|
913
|
+
distance: row.distance ?? row[4],
|
|
914
|
+
}));
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
catch {
|
|
918
|
+
bestChunks = new Map();
|
|
919
|
+
}
|
|
920
|
+
if (bestChunks.size === 0) {
|
|
921
|
+
const embeddingCount = Number(tableCheck[0].cnt ?? tableCheck[0][0] ?? 0);
|
|
922
|
+
const exactLimit = getExactScanLimit();
|
|
923
|
+
if (embeddingCount > exactLimit)
|
|
924
|
+
return [];
|
|
925
|
+
const rows = await executeQuery(repo.id, `
|
|
926
|
+
MATCH (e:${EMBEDDING_TABLE_NAME})
|
|
927
|
+
RETURN e.nodeId AS nodeId, e.chunkIndex AS chunkIndex,
|
|
928
|
+
e.startLine AS startLine, e.endLine AS endLine, e.embedding AS embedding
|
|
929
|
+
`);
|
|
930
|
+
const exactRows = rows.map((row) => ({
|
|
905
931
|
nodeId: row.nodeId ?? row[0],
|
|
906
932
|
chunkIndex: row.chunkIndex ?? row[1] ?? 0,
|
|
907
933
|
startLine: row.startLine ?? row[2] ?? 0,
|
|
908
934
|
endLine: row.endLine ?? row[3] ?? 0,
|
|
909
|
-
|
|
935
|
+
embedding: row.embedding ?? row[4] ?? [],
|
|
910
936
|
}));
|
|
911
|
-
|
|
937
|
+
bestChunks = new Map(rankExactEmbeddingRows(exactRows, queryVec, limit, 0.6).map((row) => [
|
|
938
|
+
row.nodeId,
|
|
939
|
+
{
|
|
940
|
+
distance: row.distance,
|
|
941
|
+
chunkIndex: row.chunkIndex,
|
|
942
|
+
startLine: row.startLine,
|
|
943
|
+
endLine: row.endLine,
|
|
944
|
+
},
|
|
945
|
+
]));
|
|
946
|
+
}
|
|
912
947
|
if (bestChunks.size === 0)
|
|
913
948
|
return [];
|
|
914
949
|
const results = [];
|
package/dist/server/api.d.ts
CHANGED
|
@@ -41,6 +41,11 @@ export declare class ClientDisconnectedError extends Error {
|
|
|
41
41
|
constructor();
|
|
42
42
|
}
|
|
43
43
|
export declare const isIgnorableGraphQueryError: (err: unknown) => boolean;
|
|
44
|
+
export declare const SPA_FALLBACK_REGEX: RegExp;
|
|
45
|
+
export declare const resolveWebDistDir: (primaryDir: string, fallbackDir: string) => Promise<string | null>;
|
|
46
|
+
export declare const landingPageHtml: () => string;
|
|
47
|
+
export declare const staticCacheControlSetHeaders: (res: express.Response, filePath: string) => void;
|
|
48
|
+
export declare const registerWebUI: (app: express.Express, staticDir: string | null) => void;
|
|
44
49
|
export declare const writeNdjsonRecord: (res: express.Response, record: GraphStreamRecord, signal?: AbortSignal) => Promise<void>;
|
|
45
50
|
export declare const streamGraphNdjson: (res: express.Response, includeContent?: boolean, signal?: AbortSignal) => Promise<void>;
|
|
46
51
|
export declare const createServer: (port: number, host?: string) => Promise<void>;
|
package/dist/server/api.js
CHANGED
|
@@ -103,6 +103,98 @@ export const isIgnorableGraphQueryError = (err) => {
|
|
|
103
103
|
message.includes('not found') ||
|
|
104
104
|
message.includes('No table named'));
|
|
105
105
|
};
|
|
106
|
+
export const SPA_FALLBACK_REGEX = /^(?!\/api(?:\/|$))(?!.*\.\w{1,10}$).*/;
|
|
107
|
+
export const resolveWebDistDir = async (primaryDir, fallbackDir) => {
|
|
108
|
+
const envDir = process.env.GITNEXUS_WEB_DIST;
|
|
109
|
+
const dirs = envDir ? [envDir, primaryDir, fallbackDir] : [primaryDir, fallbackDir];
|
|
110
|
+
for (const dir of dirs) {
|
|
111
|
+
try {
|
|
112
|
+
await fs.access(path.join(dir, 'index.html'));
|
|
113
|
+
return dir;
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
if (err?.code !== 'ENOENT') {
|
|
117
|
+
console.warn(`[serve] could not access web UI dir ${dir}:`, err.message);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
};
|
|
123
|
+
export const landingPageHtml = () => `<!DOCTYPE html>
|
|
124
|
+
<html lang="en">
|
|
125
|
+
<head>
|
|
126
|
+
<meta charset="utf-8">
|
|
127
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
128
|
+
<title>GitNexus</title>
|
|
129
|
+
<style>
|
|
130
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
131
|
+
body{font-family:Outfit,system-ui,-apple-system,sans-serif;background:#06060a;color:#e4e4ed;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:1.5rem}
|
|
132
|
+
.card{background:#101018;border:1px solid #2a2a3a;border-radius:0.75rem;padding:2rem;max-width:480px;width:100%}
|
|
133
|
+
.logo{font-size:1.5rem;font-weight:700;color:#e4e4ed;letter-spacing:-0.02em;margin-bottom:0.25rem}
|
|
134
|
+
.subtitle{font-size:0.875rem;color:#8888a0;margin-bottom:1.5rem}
|
|
135
|
+
.section-title{font-size:0.75rem;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;color:#5a5a70;margin-bottom:0.75rem}
|
|
136
|
+
.endpoint{margin:0.25rem 0;font-size:0.875rem}
|
|
137
|
+
.endpoint a{color:#7c3aed;text-decoration:none}
|
|
138
|
+
.endpoint a:hover{text-decoration:underline}
|
|
139
|
+
.endpoint code{background:#16161f;padding:0.15em 0.4em;border-radius:0.25rem;font-size:0.8rem;color:#8888a0}
|
|
140
|
+
.divider{height:1px;background:#1e1e2a;margin:1.25rem 0}
|
|
141
|
+
.terminal{background:#0a0a10;border:1px solid #1e1e2a;border-radius:0.5rem;padding:0.75rem 1rem;font-family:'SF Mono',SFMono-Regular,Consolas,'Liberation Mono',Menlo,monospace;font-size:0.8rem;color:#8888a0;margin-bottom:1rem;overflow-x:auto}
|
|
142
|
+
.terminal .prompt{color:#7c3aed;user-select:none}
|
|
143
|
+
.terminal .cmd{color:#e4e4ed}
|
|
144
|
+
.link-row{display:flex;align-items:center;gap:0.5rem;font-size:0.875rem;margin-top:0.5rem}
|
|
145
|
+
.link-row svg{flex-shrink:0}
|
|
146
|
+
a.ext{color:#7c3aed;text-decoration:none;display:inline-flex;align-items:center;gap:0.25rem}
|
|
147
|
+
a.ext:hover{text-decoration:underline}
|
|
148
|
+
</style>
|
|
149
|
+
</head>
|
|
150
|
+
<body>
|
|
151
|
+
<div class="card">
|
|
152
|
+
<div class="logo">GitNexus</div>
|
|
153
|
+
<div class="subtitle">API server is running</div>
|
|
154
|
+
<div class="section-title">Endpoints</div>
|
|
155
|
+
<p class="endpoint"><a href="/api/info">/api/info</a> <span style="color:#5a5a70">— Server version & context</span></p>
|
|
156
|
+
<p class="endpoint"><a href="/api/repos">/api/repos</a> <span style="color:#5a5a70">— Indexed repositories</span></p>
|
|
157
|
+
<p class="endpoint"><code>/api/heartbeat</code> <span style="color:#5a5a70">— SSE heartbeat</span></p>
|
|
158
|
+
<p class="endpoint"><code>/api/graph</code> <code>/api/query</code> <code>/api/search</code> <span style="color:#5a5a70">— Data</span></p>
|
|
159
|
+
<p class="endpoint"><code>/api/mcp</code> <span style="color:#5a5a70">— MCP over StreamableHTTP</span></p>
|
|
160
|
+
<div class="divider"></div>
|
|
161
|
+
<div class="section-title">Web UI not found</div>
|
|
162
|
+
<div class="terminal"><span class="prompt">$ </span><span class="cmd">cd gitnexus-web && npm run build</span></div>
|
|
163
|
+
<div class="link-row">
|
|
164
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#7c3aed" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
|
|
165
|
+
<a class="ext" href="https://gitnexus.vercel.app" target="_blank" rel="noopener noreferrer">gitnexus.vercel.app</a>
|
|
166
|
+
<span style="color:#5a5a70">— connects to this server</span>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</body>
|
|
170
|
+
</html>`;
|
|
171
|
+
export const staticCacheControlSetHeaders = (res, filePath) => {
|
|
172
|
+
if (filePath.endsWith('.html')) {
|
|
173
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
export const registerWebUI = (app, staticDir) => {
|
|
180
|
+
if (staticDir) {
|
|
181
|
+
app.use(express.static(staticDir, {
|
|
182
|
+
setHeaders: staticCacheControlSetHeaders,
|
|
183
|
+
}));
|
|
184
|
+
// ⚠ This must remain the LAST route before the global error handler.
|
|
185
|
+
// The regex excludes /api paths AND paths with file extensions (.js, .css, etc.)
|
|
186
|
+
// so missing assets get real 404s instead of the SPA HTML.
|
|
187
|
+
// Adding routes below this will be unreachable for non-API, non-asset paths.
|
|
188
|
+
app.get(SPA_FALLBACK_REGEX, (_req, res) => {
|
|
189
|
+
res.sendFile(path.join(staticDir, 'index.html'));
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
app.get('/', (_req, res) => {
|
|
194
|
+
res.type('html').send(landingPageHtml());
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
};
|
|
106
198
|
const ensureStreamIsWritable = (res, signal) => {
|
|
107
199
|
if (signal?.aborted || res.destroyed || res.writableEnded) {
|
|
108
200
|
throw new ClientDisconnectedError();
|
|
@@ -1373,6 +1465,15 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1373
1465
|
embedJobManager.cancelJob(req.params.jobId, 'Cancelled by user');
|
|
1374
1466
|
res.json({ id: job.id, status: 'failed', error: 'Cancelled by user' });
|
|
1375
1467
|
});
|
|
1468
|
+
// ── Web UI (served at root) ───────────────────────────────────────
|
|
1469
|
+
// Resolve the gitnexus-web dist directory relative to this file's location.
|
|
1470
|
+
// In the published package: <pkg>/dist/server/api.js → <pkg>/web/
|
|
1471
|
+
// In dev (tsx): gitnexus/src/server/api.ts → gitnexus-web/dist/
|
|
1472
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
1473
|
+
const webDistDir = path.resolve(__dirname, '..', '..', 'web');
|
|
1474
|
+
const devWebDistDir = path.resolve(__dirname, '..', '..', '..', 'gitnexus-web', 'dist');
|
|
1475
|
+
const staticDir = await resolveWebDistDir(webDistDir, devWebDistDir);
|
|
1476
|
+
registerWebUI(app, staticDir);
|
|
1376
1477
|
// Global error handler — catch anything the route handlers miss
|
|
1377
1478
|
app.use((err, _req, res, _next) => {
|
|
1378
1479
|
console.error('Unhandled error:', err);
|
|
@@ -1400,5 +1501,17 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1400
1501
|
};
|
|
1401
1502
|
process.once('SIGINT', shutdown);
|
|
1402
1503
|
process.once('SIGTERM', shutdown);
|
|
1504
|
+
// Catch-all crash guards (mirrors startMCPServer in mcp/server.ts)
|
|
1505
|
+
let shuttingDown = false;
|
|
1506
|
+
process.on('uncaughtException', (err) => {
|
|
1507
|
+
console.error('GitNexus uncaughtException:', err?.stack || err);
|
|
1508
|
+
if (!shuttingDown) {
|
|
1509
|
+
shuttingDown = true;
|
|
1510
|
+
shutdown().catch(() => { });
|
|
1511
|
+
}
|
|
1512
|
+
});
|
|
1513
|
+
process.on('unhandledRejection', (reason) => {
|
|
1514
|
+
console.error('GitNexus unhandledRejection:', reason?.stack || reason);
|
|
1515
|
+
});
|
|
1403
1516
|
});
|
|
1404
1517
|
};
|
|
@@ -31,11 +31,21 @@ function readInput() {
|
|
|
31
31
|
* Find the .gitnexus directory by walking up from startDir.
|
|
32
32
|
* Returns the path to .gitnexus/ or null if not found.
|
|
33
33
|
*/
|
|
34
|
+
function isGlobalRegistryDir(candidate) {
|
|
35
|
+
if (fs.existsSync(path.join(candidate, 'meta.json'))) return false;
|
|
36
|
+
return (
|
|
37
|
+
fs.existsSync(path.join(candidate, 'registry.json')) ||
|
|
38
|
+
fs.existsSync(path.join(candidate, 'repos'))
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
34
42
|
function findGitNexusDir(startDir) {
|
|
35
43
|
let dir = startDir || process.cwd();
|
|
36
44
|
for (let i = 0; i < 5; i++) {
|
|
37
45
|
const candidate = path.join(dir, '.gitnexus');
|
|
38
|
-
if (fs.existsSync(candidate))
|
|
46
|
+
if (fs.existsSync(candidate)) {
|
|
47
|
+
if (!isGlobalRegistryDir(candidate)) return candidate;
|
|
48
|
+
}
|
|
39
49
|
const parent = path.dirname(dir);
|
|
40
50
|
if (parent === dir) break;
|
|
41
51
|
dir = parent;
|