gitnexus 1.6.10-rc.2 → 1.6.10-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 +56 -12
- package/dist/cli/analyze.js +166 -5
- package/dist/cli/clean.js +17 -3
- package/dist/cli/cli-message.d.ts +1 -1
- package/dist/cli/doctor.d.ts +18 -0
- package/dist/cli/doctor.js +76 -3
- package/dist/cli/embeddings.d.ts +12 -0
- package/dist/cli/embeddings.js +49 -0
- package/dist/cli/help-i18n.js +4 -0
- package/dist/cli/i18n/en.d.ts +10 -5
- package/dist/cli/i18n/en.js +10 -5
- package/dist/cli/i18n/resources.d.ts +15 -5
- package/dist/cli/i18n/zh-CN.d.ts +5 -0
- package/dist/cli/i18n/zh-CN.js +10 -5
- package/dist/cli/index.js +12 -1
- package/dist/core/augmentation/engine.js +4 -3
- package/dist/core/embeddings/embedder.js +16 -2
- package/dist/core/embeddings/embedding-pipeline.d.ts +20 -0
- package/dist/core/embeddings/embedding-pipeline.js +10 -2
- package/dist/core/embeddings/http-client.d.ts +28 -1
- package/dist/core/embeddings/http-client.js +107 -15
- package/dist/core/embeddings/node-module-compat.d.ts +23 -0
- package/dist/core/embeddings/node-module-compat.js +23 -0
- package/dist/core/embeddings/onnxruntime-common-resolver.js +6 -3
- package/dist/core/embeddings/onnxruntime-node-resolver.js +31 -5
- package/dist/core/embeddings/runtime-install.d.ts +119 -0
- package/dist/core/embeddings/runtime-install.js +372 -0
- package/dist/core/embeddings/runtime-support.d.ts +48 -17
- package/dist/core/embeddings/runtime-support.js +96 -0
- package/dist/core/group/extractors/http-patterns/python.js +246 -108
- package/dist/core/group/extractors/manifest-extractor.js +4 -0
- package/dist/core/incremental/escalation-gate.d.ts +38 -0
- package/dist/core/incremental/escalation-gate.js +48 -0
- package/dist/core/incremental/shadow-candidates.d.ts +3 -3
- package/dist/core/incremental/shadow-candidates.js +3 -3
- package/dist/core/incremental/subgraph-extract.d.ts +2 -2
- package/dist/core/incremental/subgraph-extract.js +2 -2
- package/dist/core/ingestion/cobol/jcl-processor.js +9 -8
- package/dist/core/ingestion/cobol-processor.js +27 -26
- package/dist/core/ingestion/community-processor.d.ts +51 -1
- package/dist/core/ingestion/community-processor.js +316 -63
- package/dist/core/ingestion/emit-references.js +3 -2
- package/dist/core/ingestion/languages/cpp/captures.js +157 -74
- package/dist/core/ingestion/markdown-processor.js +3 -2
- package/dist/core/ingestion/parsing-processor.d.ts +3 -1
- package/dist/core/ingestion/parsing-processor.js +4 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +42 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.d.ts +83 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.js +131 -0
- package/dist/core/ingestion/route-extractors/python-const-resolver.d.ts +75 -0
- package/dist/core/ingestion/route-extractors/python-const-resolver.js +299 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
- package/dist/core/ingestion/tree-sitter-queries.js +12 -1
- package/dist/core/ingestion/utils/line-base.d.ts +20 -0
- package/dist/core/ingestion/utils/line-base.js +20 -0
- package/dist/core/ingestion/utils/symbol-labels.d.ts +21 -0
- package/dist/core/ingestion/utils/symbol-labels.js +45 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +34 -0
- package/dist/core/ingestion/workers/parse-worker.js +40 -5
- package/dist/core/ingestion/workers/result-merge.js +4 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +14 -0
- package/dist/core/ingestion/workers/worker-pool.js +85 -5
- package/dist/core/lbug/csv-generator.js +8 -2
- package/dist/core/lbug/cypher-escape.d.ts +20 -0
- package/dist/core/lbug/cypher-escape.js +20 -0
- package/dist/core/lbug/extension-load-error.d.ts +67 -0
- package/dist/core/lbug/extension-load-error.js +320 -0
- package/dist/core/lbug/extension-loader.d.ts +17 -2
- package/dist/core/lbug/extension-loader.js +38 -13
- package/dist/core/lbug/lbug-adapter.d.ts +147 -1
- package/dist/core/lbug/lbug-adapter.js +390 -70
- package/dist/core/lbug/lbug-config.d.ts +28 -0
- package/dist/core/lbug/lbug-config.js +114 -4
- package/dist/core/lbug/native-check.d.ts +22 -0
- package/dist/core/lbug/native-check.js +66 -0
- package/dist/core/lbug/pool-adapter.js +10 -1
- package/dist/core/lbug/sidecar-recovery.d.ts +172 -0
- package/dist/core/lbug/sidecar-recovery.js +418 -16
- package/dist/core/platform/capabilities.js +27 -1
- package/dist/core/run-analyze.js +470 -108
- package/dist/core/search/fts-indexes.d.ts +7 -0
- package/dist/core/search/fts-indexes.js +37 -0
- package/dist/core/tree-sitter/safe-parse.d.ts +8 -3
- package/dist/core/tree-sitter/safe-parse.js +21 -7
- package/dist/core/wiki/graph-queries.js +6 -5
- package/dist/mcp/core/embedder.js +16 -2
- package/dist/mcp/local/line-display.d.ts +22 -0
- package/dist/mcp/local/line-display.js +3 -0
- package/dist/mcp/local/local-backend.d.ts +14 -0
- package/dist/mcp/local/local-backend.js +63 -19
- package/dist/mcp/local/pdg-impact.d.ts +5 -3
- package/dist/mcp/local/pdg-impact.js +5 -2
- package/dist/mcp/resources.js +1 -0
- package/dist/mcp/tools.js +1 -1
- package/dist/server/analyze-launch.d.ts +6 -0
- package/dist/server/analyze-launch.js +82 -6
- package/dist/server/api.d.ts +30 -0
- package/dist/server/api.js +63 -21
- package/dist/storage/parse-cache.js +1 -1
- package/dist/storage/repo-manager.d.ts +65 -1
- package/dist/storage/repo-manager.js +18 -1
- package/hooks/antigravity/gitnexus-antigravity-hook.cjs +50 -3
- package/hooks/claude/gitnexus-hook.cjs +59 -9
- package/package.json +5 -3
- package/scripts/cross-platform-tests.ts +43 -0
- package/scripts/ensure-fts.ts +32 -0
- package/scripts/install-duckdb-extension.mjs +85 -24
- package/scripts/run-cross-platform.ts +46 -6
- package/scripts/shard-arg.ts +30 -0
- package/web/assets/ProcessFlowModal-Dna7GkwO.js +22 -0
- package/web/assets/{agent-Dli_x7bU.js → agent-DNYDy-KA.js} +49 -49
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-BVWFvYz2.js +1 -0
- package/web/assets/architectureDiagram-UL44E2DR-BU5ZRITg.js +36 -0
- package/web/assets/{blockDiagram-7IZFK4PR-BkWhpzWB.js → blockDiagram-7IZFK4PR-Dut1BpTV.js} +2 -2
- package/web/assets/{c4Diagram-Y2BXMSZH-DtBvpzWD.js → c4Diagram-Y2BXMSZH-DOXVs7-C.js} +2 -2
- package/web/assets/chunk-2T2R6R2M-pkw2oXHH.js +4 -0
- package/web/assets/chunk-2UTLFMKG-CXlhF3Pt.js +1 -0
- package/web/assets/chunk-3SSMPTDK-rPsRM1x0.js +321 -0
- package/web/assets/chunk-4R4BOZG6-Be_F0JoX.js +159 -0
- package/web/assets/{chunk-6764PJDD-DpIS7hxD.js → chunk-6764PJDD-C7xxk063.js} +1 -1
- package/web/assets/{chunk-67TQ5CYL-4sNPJU1q.js → chunk-67TQ5CYL-zxhDWgyF.js} +3 -3
- package/web/assets/chunk-7FYTHRHK-CsmYIswB.js +32 -0
- package/web/assets/chunk-7J6CGLKN-AmkN8jJN.js +10 -0
- package/web/assets/chunk-7W6UQGC5-pyKGssV3.js +1 -0
- package/web/assets/chunk-C62D2QBJ-3gdL8EKc.js +1 -0
- package/web/assets/chunk-CEXFNPSA-CjwtMDkc.js +1 -0
- package/web/assets/{chunk-INKRHTLW-Nu4ri9P-.js → chunk-INKRHTLW-D9BM9HWZ.js} +1 -1
- package/web/assets/chunk-J5EP6P6S-L19oz55R.js +1 -0
- package/web/assets/{chunk-KGFNY3KK-JGMJvKCK.js → chunk-KGFNY3KK-tD2F6wxK.js} +1 -1
- package/web/assets/chunk-KGYTTC2M-zm6n242I.js +1 -0
- package/web/assets/{chunk-KRXBNO2N-BW5o0KUN.js → chunk-KRXBNO2N-C-UNuKKf.js} +1 -1
- package/web/assets/{chunk-LCXTWHL2-CfLLCywY.js → chunk-LCXTWHL2-Ba9JK-6F.js} +2 -2
- package/web/assets/{chunk-LII3EMHJ-D-jm-dLa.js → chunk-LII3EMHJ-EGx-eBIu.js} +1 -1
- package/web/assets/chunk-LRIF4GLE-C3_PqUBq.js +1 -0
- package/web/assets/{chunk-QA3QBVWF-BgRW3SXd.js → chunk-QA3QBVWF-D4cSJBf6.js} +1 -1
- package/web/assets/chunk-RERM46MO-B5dAdWpP.js +1 -0
- package/web/assets/{chunk-RG4AUYOV-CqWzWsV3.js → chunk-RG4AUYOV-OqBBMwuQ.js} +4 -4
- package/web/assets/chunk-RKZBBQEN-MyIQbGgD.js +1 -0
- package/web/assets/chunk-RLI5ZMPA-CkXZ0y0U.js +1 -0
- package/web/assets/{chunk-T2UQINTJ-DSBqkzH7.js → chunk-T2UQINTJ-BJusEiGe.js} +1 -1
- package/web/assets/chunk-UP6H54XL-BG-n7qDp.js +1 -0
- package/web/assets/chunk-UXSXWOXI-CW1_r5cS.js +1 -0
- package/web/assets/{chunk-UY5QBCOK-DyF0vyGd.js → chunk-UY5QBCOK-BCpUlARQ.js} +1 -1
- package/web/assets/chunk-VU6ZFW4Y-BeXVk45_.js +2 -0
- package/web/assets/{chunk-W44A43WB-JqMLIpR0.js → chunk-W44A43WB-BN8EQc_u.js} +1 -1
- package/web/assets/{chunk-ZXARS5L4-CxN8oiwI.js → chunk-ZXARS5L4-BI_qR2Nm.js} +1 -1
- package/web/assets/classDiagram-KGZ6W3CR-D9zSIg0x.js +1 -0
- package/web/assets/classDiagram-v2-72OJOZXJ-D9zSIg0x.js +1 -0
- package/web/assets/cose-bilkent-UX7MHV2Q-BmnZ7Mvj.js +1 -0
- package/web/assets/dagre-ND4H6XIP-CpcV5kbl.js +4 -0
- package/web/assets/diagram-3NCE3AQN-6SMxBRpj.js +43 -0
- package/web/assets/diagram-GF46GFSD-BZEdNcRZ.js +24 -0
- package/web/assets/{diagram-HNR7UZ2L-DFCfd5LI.js → diagram-HNR7UZ2L-DxKW2a2Y.js} +2 -2
- package/web/assets/diagram-QXG6HAR7-HfTHcXX_.js +24 -0
- package/web/assets/diagram-WEQXMOUZ-CyijUS8r.js +10 -0
- package/web/assets/{erDiagram-L5TCEMPS-8X1y6R4Y.js → erDiagram-L5TCEMPS-D0fvpWEX.js} +3 -3
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-CgFBM7e-.js +1 -0
- package/web/assets/flowDiagram-H6V6AXG4-Bvr9bbhk.js +162 -0
- package/web/assets/{ganttDiagram-JCBTUEKG-D3iL2Aet.js → ganttDiagram-JCBTUEKG-TzODrLzv.js} +2 -2
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DCJv7SII.js +1 -0
- package/web/assets/gitGraphDiagram-S2ZK5IYY-ONQSwJIY.js +106 -0
- package/web/assets/index-B4eB4dNZ.js +575 -0
- package/web/assets/index-CX_fADmQ.css +2 -0
- package/web/assets/info-J43DQDTF-KCYPFFUO-BfNy-9Nd.js +1 -0
- package/web/assets/infoDiagram-3YFTVSEB-DIPVUIQ6.js +2 -0
- package/web/assets/{ishikawaDiagram-BNXS4ZKH-DfYpxnE5.js → ishikawaDiagram-BNXS4ZKH-BLjivjXL.js} +3 -3
- package/web/assets/{journeyDiagram-M6C3CM3L-CvArszLo.js → journeyDiagram-M6C3CM3L-Cetgs3NH.js} +3 -3
- package/web/assets/{kanban-definition-75IXJCU3-CpfVLLf9.js → kanban-definition-75IXJCU3-DMlCaIpf.js} +5 -5
- package/web/assets/{katex-K3KEBU37-CTc5BslQ.js → katex-K3KEBU37-qRQBy-EZ.js} +2 -2
- package/web/assets/{mindmap-definition-2TDM6QVE-DypvbOMQ.js → mindmap-definition-2TDM6QVE-B4GbXQBc.js} +3 -3
- package/web/assets/packet-YPE3B663-LP52Z2RK-D4qkeG9k.js +1 -0
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-B_yMKyQa.js +1 -0
- package/web/assets/{pieDiagram-CU6KROY3-s27EFWgP.js → pieDiagram-CU6KROY3-D3YC_xyg.js} +2 -2
- package/web/assets/{quadrantDiagram-VICAPDV7-yXSZ2lLz.js → quadrantDiagram-VICAPDV7-B1TQnlm0.js} +2 -2
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-DeD6yN_0.js +1 -0
- package/web/assets/{requirementDiagram-JXO7QTGE-CWhqInuf.js → requirementDiagram-JXO7QTGE-f8wczLQf.js} +3 -3
- package/web/assets/rolldown-runtime-QTnfLwEv.js +1 -0
- package/web/assets/{sankeyDiagram-URQDO5SZ-1qEjlFIB.js → sankeyDiagram-URQDO5SZ-BTxzG5j0.js} +2 -2
- package/web/assets/{sequenceDiagram-VS2MUI6T-RyTlZ5cR.js → sequenceDiagram-VS2MUI6T-DuamM-IR.js} +5 -5
- package/web/assets/src-BNCXICdA.js +1 -0
- package/web/assets/stateDiagram-7D4R322I-Ch2pEUcw.js +1 -0
- package/web/assets/stateDiagram-v2-36443NZ5-7ANuoZLL.js +1 -0
- package/web/assets/{timeline-definition-O6YCAMPW-DrrqLYAf.js → timeline-definition-O6YCAMPW-07BR8R8G.js} +3 -3
- package/web/assets/treeView-BLDUP644-QA4HXRO3-CDuSYnd4.js +1 -0
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-imazucyJ.js +1 -0
- package/web/assets/{vennDiagram-MWXL3ELB-B0RkWAim.js → vennDiagram-MWXL3ELB-BQX2ossk.js} +4 -4
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-DqrT9tB8.js +1 -0
- package/web/assets/{wardleyDiagram-CUQ6CDDI-boUxiQ5F.js → wardleyDiagram-CUQ6CDDI-CGIJBBiK.js} +3 -3
- package/web/assets/{xychartDiagram-N2JHSOCM-BOLm0eNm.js → xychartDiagram-N2JHSOCM-B3HNMuWp.js} +4 -4
- package/web/index.html +17 -10
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +0 -1
- package/web/assets/architectureDiagram-UL44E2DR-dEkqUZN9.js +0 -36
- package/web/assets/chunk-3SSMPTDK-B4_etUhr.js +0 -321
- package/web/assets/chunk-7W6UQGC5-KyEG0HQg.js +0 -1
- package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +0 -161
- package/web/assets/classDiagram-KGZ6W3CR-CMne4tG9.js +0 -1
- package/web/assets/classDiagram-v2-72OJOZXJ-CMne4tG9.js +0 -1
- package/web/assets/cose-bilkent-UX7MHV2Q-D71wNYRJ.js +0 -1
- package/web/assets/dagre-ND4H6XIP-Co5rRx9X.js +0 -4
- package/web/assets/diagram-3NCE3AQN-DBp4O00j.js +0 -43
- package/web/assets/diagram-GF46GFSD-6N_8yS0i.js +0 -24
- package/web/assets/diagram-QXG6HAR7-Di093rFg.js +0 -24
- package/web/assets/diagram-WEQXMOUZ-DZWi4SUN.js +0 -10
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +0 -1
- package/web/assets/flowDiagram-H6V6AXG4-CMpY8Ufn.js +0 -162
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +0 -1
- package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +0 -106
- package/web/assets/index-KctnI3Vq.js +0 -635
- package/web/assets/index-_lgn7hs5.css +0 -2
- package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +0 -1
- package/web/assets/infoDiagram-3YFTVSEB-BaLMZkUg.js +0 -2
- package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +0 -1
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +0 -1
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +0 -1
- package/web/assets/stateDiagram-7D4R322I-DTjVfX4A.js +0 -1
- package/web/assets/stateDiagram-v2-36443NZ5-xY9_k9_Q.js +0 -1
- package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +0 -1
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +0 -1
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +0 -1
|
@@ -14,6 +14,7 @@ import Graph from 'graphology';
|
|
|
14
14
|
import { createRequire } from 'node:module';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
16
|
import { dirname, resolve } from 'node:path';
|
|
17
|
+
import { Worker } from 'node:worker_threads';
|
|
17
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
18
19
|
const __dirname = dirname(__filename);
|
|
19
20
|
// Navigate to package root (works from both src/ and dist/)
|
|
@@ -37,6 +38,20 @@ function createSeededRng(seed) {
|
|
|
37
38
|
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
38
39
|
};
|
|
39
40
|
}
|
|
41
|
+
const COMMUNITY_ENGINE_ENV = 'GITNEXUS_COMMUNITY_ENGINE';
|
|
42
|
+
const DEFAULT_COMMUNITY_ENGINE = 'graphology';
|
|
43
|
+
const LEIDEN_TIMEOUT_MS = 60_000;
|
|
44
|
+
const ICEBUG_TIMEOUT_MS = 60_000;
|
|
45
|
+
const MIN_CONFIDENCE_LARGE = 0.5;
|
|
46
|
+
export const resolveCommunityDetectionEngine = (raw = process.env[COMMUNITY_ENGINE_ENV]) => {
|
|
47
|
+
if (raw === undefined || raw.trim() === '')
|
|
48
|
+
return DEFAULT_COMMUNITY_ENGINE;
|
|
49
|
+
const normalized = raw.trim().toLowerCase();
|
|
50
|
+
if (normalized === 'graphology' || normalized === 'icebug' || normalized === 'auto') {
|
|
51
|
+
return normalized;
|
|
52
|
+
}
|
|
53
|
+
return DEFAULT_COMMUNITY_ENGINE;
|
|
54
|
+
};
|
|
40
55
|
// ============================================================================
|
|
41
56
|
// COMMUNITY COLORS (for visualization)
|
|
42
57
|
// ============================================================================
|
|
@@ -66,59 +81,28 @@ export const getCommunityColor = (communityIndex) => {
|
|
|
66
81
|
* This runs AFTER all relationships (CALLS, IMPORTS, etc.) have been built.
|
|
67
82
|
* It uses primarily CALLS edges to cluster code that works together.
|
|
68
83
|
*/
|
|
69
|
-
export const processCommunities = async (knowledgeGraph, onProgress) => {
|
|
84
|
+
export const processCommunities = async (knowledgeGraph, onProgress, options = {}) => {
|
|
70
85
|
onProgress?.('Building graph for community detection...', 0);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (node.label === 'Function' ||
|
|
75
|
-
node.label === 'Class' ||
|
|
76
|
-
node.label === 'Method' ||
|
|
77
|
-
node.label === 'Interface') {
|
|
78
|
-
symbolCount++;
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
const isLarge = symbolCount > 10_000;
|
|
82
|
-
const graph = buildGraphologyGraph(knowledgeGraph, isLarge);
|
|
86
|
+
const engineRequested = options.engine ?? resolveCommunityDetectionEngine();
|
|
87
|
+
const projection = buildCommunityProjection(knowledgeGraph);
|
|
88
|
+
const graph = buildGraphologyGraph(projection);
|
|
83
89
|
if (graph.order === 0) {
|
|
84
90
|
return {
|
|
85
91
|
communities: [],
|
|
86
92
|
memberships: [],
|
|
87
|
-
stats: {
|
|
93
|
+
stats: {
|
|
94
|
+
totalCommunities: 0,
|
|
95
|
+
modularity: 0,
|
|
96
|
+
nodesProcessed: 0,
|
|
97
|
+
engine: DEFAULT_COMMUNITY_ENGINE,
|
|
98
|
+
engineRequested,
|
|
99
|
+
},
|
|
88
100
|
};
|
|
89
101
|
}
|
|
90
102
|
const nodeCount = graph.order;
|
|
91
103
|
const edgeCount = graph.size;
|
|
92
|
-
onProgress?.(`Running Leiden on ${nodeCount} nodes, ${edgeCount} edges${isLarge ? ` (filtered from ${symbolCount} symbols)` : ''}...`, 30);
|
|
93
|
-
|
|
94
|
-
// The first 2 iterations capture ~95%+ of modularity; additional iterations have diminishing returns.
|
|
95
|
-
// Timeout: abort after 60s for pathological graph structures.
|
|
96
|
-
const LEIDEN_TIMEOUT_MS = 60_000;
|
|
97
|
-
let details;
|
|
98
|
-
try {
|
|
99
|
-
details = await Promise.race([
|
|
100
|
-
Promise.resolve(leiden.detailed(graph, {
|
|
101
|
-
resolution: isLarge ? 2.0 : 1.0,
|
|
102
|
-
maxIterations: isLarge ? 3 : 0,
|
|
103
|
-
rng: createSeededRng(LEIDEN_SEED),
|
|
104
|
-
})),
|
|
105
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('Leiden timeout')), LEIDEN_TIMEOUT_MS)),
|
|
106
|
-
]);
|
|
107
|
-
}
|
|
108
|
-
catch (e) {
|
|
109
|
-
if (e.message === 'Leiden timeout') {
|
|
110
|
-
onProgress?.('Community detection timed out, using fallback...', 60);
|
|
111
|
-
// Fallback: assign all nodes to community 0
|
|
112
|
-
const communities = {};
|
|
113
|
-
graph.forEachNode((node) => {
|
|
114
|
-
communities[node] = 0;
|
|
115
|
-
});
|
|
116
|
-
details = { communities, count: 1, modularity: 0 };
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
throw e;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
104
|
+
onProgress?.(`Running Leiden on ${nodeCount} nodes, ${edgeCount} edges${projection.isLarge ? ` (filtered from ${projection.symbolCount} symbols)` : ''}...`, 30);
|
|
105
|
+
const details = await runCommunityEngine(graph, projection, engineRequested, options, onProgress);
|
|
122
106
|
onProgress?.(`Found ${details.count} communities...`, 60);
|
|
123
107
|
// Step 3: Create community nodes with heuristic labels
|
|
124
108
|
const communityNodes = createCommunityNodes(details.communities, details.count, graph, knowledgeGraph);
|
|
@@ -139,27 +123,32 @@ export const processCommunities = async (knowledgeGraph, onProgress) => {
|
|
|
139
123
|
totalCommunities: details.count,
|
|
140
124
|
modularity: details.modularity,
|
|
141
125
|
nodesProcessed: graph.order,
|
|
126
|
+
engine: details.engine,
|
|
127
|
+
engineRequested: details.engineRequested,
|
|
128
|
+
fallbackReason: details.fallbackReason,
|
|
142
129
|
},
|
|
143
130
|
};
|
|
144
131
|
};
|
|
145
132
|
// ============================================================================
|
|
146
|
-
// HELPER: Build
|
|
133
|
+
// HELPER: Build community projection from knowledge graph
|
|
147
134
|
// ============================================================================
|
|
148
135
|
/**
|
|
149
|
-
* Build a
|
|
136
|
+
* Build a community projection containing only symbol nodes and clustering edges.
|
|
150
137
|
* For large graphs (>10K symbols), filter out low-confidence fuzzy-global edges
|
|
151
138
|
* and degree-1 nodes that add noise and massively increase Leiden runtime.
|
|
152
139
|
*/
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
140
|
+
export const buildCommunityProjection = (knowledgeGraph) => {
|
|
141
|
+
let symbolCount = 0;
|
|
142
|
+
knowledgeGraph.forEachNode((node) => {
|
|
143
|
+
if (isCommunitySymbol(node)) {
|
|
144
|
+
symbolCount++;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
const isLarge = symbolCount > 10_000;
|
|
159
148
|
const connectedNodes = new Set();
|
|
160
149
|
const nodeDegree = new Map();
|
|
161
150
|
knowledgeGraph.forEachRelationship((rel) => {
|
|
162
|
-
if (!
|
|
151
|
+
if (!isClusteringRelationship(rel.type) || rel.sourceId === rel.targetId)
|
|
163
152
|
return;
|
|
164
153
|
if (isLarge && rel.confidence < MIN_CONFIDENCE_LARGE)
|
|
165
154
|
return;
|
|
@@ -168,34 +157,298 @@ const buildGraphologyGraph = (knowledgeGraph, isLarge) => {
|
|
|
168
157
|
nodeDegree.set(rel.sourceId, (nodeDegree.get(rel.sourceId) || 0) + 1);
|
|
169
158
|
nodeDegree.set(rel.targetId, (nodeDegree.get(rel.targetId) || 0) + 1);
|
|
170
159
|
});
|
|
160
|
+
const nodes = [];
|
|
161
|
+
const nodeIndexById = new Map();
|
|
171
162
|
knowledgeGraph.forEachNode((node) => {
|
|
172
|
-
if (!
|
|
163
|
+
if (!isCommunitySymbol(node) || !connectedNodes.has(node.id))
|
|
173
164
|
return;
|
|
174
165
|
// For large graphs, skip degree-1 nodes — they just become singletons or
|
|
175
166
|
// get absorbed into their single neighbor's community, but cost iteration time.
|
|
176
167
|
if (isLarge && (nodeDegree.get(node.id) || 0) < 2)
|
|
177
168
|
return;
|
|
178
|
-
|
|
169
|
+
nodeIndexById.set(node.id, nodes.length);
|
|
170
|
+
nodes.push({
|
|
171
|
+
id: node.id,
|
|
179
172
|
name: node.properties.name,
|
|
180
173
|
filePath: node.properties.filePath,
|
|
181
174
|
type: node.label,
|
|
182
175
|
});
|
|
183
176
|
});
|
|
177
|
+
const seenEdges = new Set();
|
|
178
|
+
const edges = [];
|
|
184
179
|
knowledgeGraph.forEachRelationship((rel) => {
|
|
185
|
-
if (!
|
|
180
|
+
if (!isClusteringRelationship(rel.type) || rel.sourceId === rel.targetId)
|
|
186
181
|
return;
|
|
187
182
|
if (isLarge && rel.confidence < MIN_CONFIDENCE_LARGE)
|
|
188
183
|
return;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
184
|
+
const sourceIndex = nodeIndexById.get(rel.sourceId);
|
|
185
|
+
const targetIndex = nodeIndexById.get(rel.targetId);
|
|
186
|
+
if (sourceIndex === undefined || targetIndex === undefined || sourceIndex === targetIndex)
|
|
187
|
+
return;
|
|
188
|
+
const [a, b] = sourceIndex < targetIndex ? [sourceIndex, targetIndex] : [targetIndex, sourceIndex];
|
|
189
|
+
const edgeKey = `${a}:${b}`;
|
|
190
|
+
if (seenEdges.has(edgeKey))
|
|
191
|
+
return;
|
|
192
|
+
seenEdges.add(edgeKey);
|
|
193
|
+
edges.push([a, b]);
|
|
196
194
|
});
|
|
195
|
+
return { nodes, edges, symbolCount, isLarge };
|
|
196
|
+
};
|
|
197
|
+
export const buildCommunityCsr = (projection) => {
|
|
198
|
+
const adjacency = Array.from({ length: projection.nodes.length }, () => new Set());
|
|
199
|
+
for (const [sourceIndex, targetIndex] of projection.edges) {
|
|
200
|
+
adjacency[sourceIndex].add(targetIndex);
|
|
201
|
+
adjacency[targetIndex].add(sourceIndex);
|
|
202
|
+
}
|
|
203
|
+
const edgeTraversalCount = adjacency.reduce((count, neighbors) => count + neighbors.size, 0);
|
|
204
|
+
const indptr = new BigUint64Array(projection.nodes.length + 1);
|
|
205
|
+
const indices = new BigUint64Array(edgeTraversalCount);
|
|
206
|
+
let cursor = 0;
|
|
207
|
+
for (let nodeIndex = 0; nodeIndex < adjacency.length; nodeIndex++) {
|
|
208
|
+
indptr[nodeIndex] = BigInt(cursor);
|
|
209
|
+
const neighbors = [...adjacency[nodeIndex]].sort((a, b) => a - b);
|
|
210
|
+
for (const neighbor of neighbors) {
|
|
211
|
+
indices[cursor++] = BigInt(neighbor);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
indptr[projection.nodes.length] = BigInt(cursor);
|
|
215
|
+
return { indptr, indices };
|
|
216
|
+
};
|
|
217
|
+
export const buildGraphologyGraph = (projection) => {
|
|
218
|
+
const GraphCtor = Graph;
|
|
219
|
+
const graph = new GraphCtor({ type: 'undirected', allowSelfLoops: false });
|
|
220
|
+
for (const node of projection.nodes) {
|
|
221
|
+
graph.addNode(node.id, {
|
|
222
|
+
name: node.name,
|
|
223
|
+
filePath: node.filePath,
|
|
224
|
+
type: node.type,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
for (const [sourceIndex, targetIndex] of projection.edges) {
|
|
228
|
+
graph.addEdge(projection.nodes[sourceIndex].id, projection.nodes[targetIndex].id);
|
|
229
|
+
}
|
|
197
230
|
return graph;
|
|
198
231
|
};
|
|
232
|
+
const isCommunitySymbol = (node) => node.label === 'Function' ||
|
|
233
|
+
node.label === 'Class' ||
|
|
234
|
+
node.label === 'Method' ||
|
|
235
|
+
node.label === 'Interface';
|
|
236
|
+
const isClusteringRelationship = (type) => type === 'CALLS' || type === 'EXTENDS' || type === 'IMPLEMENTS';
|
|
237
|
+
const runCommunityEngine = async (graph, projection, engineRequested, options, onProgress) => {
|
|
238
|
+
if (engineRequested === 'graphology') {
|
|
239
|
+
return runGraphologyLeiden(graph, projection.isLarge, engineRequested);
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
return await runIcebugLeiden(projection, engineRequested, options);
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
const fallbackReason = error instanceof Error ? error.message : String(error);
|
|
246
|
+
onProgress?.(`Icebug community engine unavailable, falling back to Graphology: ${fallbackReason}`, 35);
|
|
247
|
+
const fallback = await runGraphologyLeiden(graph, projection.isLarge, engineRequested);
|
|
248
|
+
return { ...fallback, fallbackReason };
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
const runGraphologyLeiden = async (graph, isLarge, engineRequested) => {
|
|
252
|
+
try {
|
|
253
|
+
const details = await Promise.race([
|
|
254
|
+
Promise.resolve(leiden.detailed(graph, {
|
|
255
|
+
resolution: isLarge ? 2.0 : 1.0,
|
|
256
|
+
maxIterations: isLarge ? 3 : 0,
|
|
257
|
+
rng: createSeededRng(LEIDEN_SEED),
|
|
258
|
+
})),
|
|
259
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Leiden timeout')), LEIDEN_TIMEOUT_MS)),
|
|
260
|
+
]);
|
|
261
|
+
return { ...details, engine: 'graphology', engineRequested };
|
|
262
|
+
}
|
|
263
|
+
catch (e) {
|
|
264
|
+
if (e.message !== 'Leiden timeout') {
|
|
265
|
+
throw e;
|
|
266
|
+
}
|
|
267
|
+
// Fallback: assign all nodes to community 0
|
|
268
|
+
const communities = {};
|
|
269
|
+
graph.forEachNode((node) => {
|
|
270
|
+
communities[node] = 0;
|
|
271
|
+
});
|
|
272
|
+
return {
|
|
273
|
+
communities,
|
|
274
|
+
count: 1,
|
|
275
|
+
modularity: 0,
|
|
276
|
+
engine: 'graphology',
|
|
277
|
+
engineRequested,
|
|
278
|
+
fallbackReason: 'Graphology Leiden timeout',
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
const runIcebugLeiden = async (projection, engineRequested, options) => {
|
|
283
|
+
const csr = buildCommunityCsr(projection);
|
|
284
|
+
const nativeResult = await runIcebugWorker(projection.nodes.length, csr, options);
|
|
285
|
+
const partition = nativeResult.partition;
|
|
286
|
+
if (!Number.isFinite(nativeResult.modularity)) {
|
|
287
|
+
throw new Error('optional icebug modularity was not finite');
|
|
288
|
+
}
|
|
289
|
+
if (partition.length !== projection.nodes.length ||
|
|
290
|
+
partition.some((community) => !Number.isSafeInteger(community))) {
|
|
291
|
+
throw new Error(`optional icebug partition was malformed for ${projection.nodes.length} projected nodes`);
|
|
292
|
+
}
|
|
293
|
+
const communities = normalizePartition(projection, partition);
|
|
294
|
+
return {
|
|
295
|
+
communities,
|
|
296
|
+
count: new Set(Object.values(communities)).size,
|
|
297
|
+
modularity: nativeResult.modularity,
|
|
298
|
+
engine: 'icebug',
|
|
299
|
+
engineRequested,
|
|
300
|
+
};
|
|
301
|
+
};
|
|
302
|
+
const runIcebugWorker = (nodeCount, csr, options) => {
|
|
303
|
+
const threads = options.icebug?.threads ?? 1;
|
|
304
|
+
if (!Number.isSafeInteger(threads) || threads !== 1) {
|
|
305
|
+
throw new Error('optional icebug engine currently requires deterministic threads=1');
|
|
306
|
+
}
|
|
307
|
+
if (options.icebug?.randomize === true) {
|
|
308
|
+
throw new Error('optional icebug engine currently requires randomize=false');
|
|
309
|
+
}
|
|
310
|
+
const worker = new Worker(ICEBUG_WORKER_SOURCE, {
|
|
311
|
+
eval: true,
|
|
312
|
+
workerData: {
|
|
313
|
+
nodeCount,
|
|
314
|
+
indices: csr.indices,
|
|
315
|
+
indptr: csr.indptr,
|
|
316
|
+
threads,
|
|
317
|
+
seed: options.icebug?.seed ?? LEIDEN_SEED,
|
|
318
|
+
iterations: options.icebug?.iterations ?? 4,
|
|
319
|
+
gamma: options.icebug?.gamma ?? 1.0,
|
|
320
|
+
randomize: options.icebug?.randomize ?? false,
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
return new Promise((resolve, reject) => {
|
|
324
|
+
let settled = false;
|
|
325
|
+
const timeout = setTimeout(() => {
|
|
326
|
+
settled = true;
|
|
327
|
+
void worker.terminate();
|
|
328
|
+
reject(new Error(`optional icebug community engine timed out after ${ICEBUG_TIMEOUT_MS}ms`));
|
|
329
|
+
}, ICEBUG_TIMEOUT_MS);
|
|
330
|
+
worker.once('message', (message) => {
|
|
331
|
+
settled = true;
|
|
332
|
+
clearTimeout(timeout);
|
|
333
|
+
void worker.terminate();
|
|
334
|
+
if (message.ok === true) {
|
|
335
|
+
resolve(message);
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
reject(new Error(message.error));
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
worker.once('error', (error) => {
|
|
342
|
+
settled = true;
|
|
343
|
+
clearTimeout(timeout);
|
|
344
|
+
void worker.terminate();
|
|
345
|
+
reject(error);
|
|
346
|
+
});
|
|
347
|
+
worker.once('exit', (code) => {
|
|
348
|
+
if (settled)
|
|
349
|
+
return;
|
|
350
|
+
clearTimeout(timeout);
|
|
351
|
+
if (code === 0) {
|
|
352
|
+
reject(new Error('optional icebug worker exited before returning a partition'));
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
reject(new Error(`optional icebug worker exited with code ${code}`));
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
};
|
|
359
|
+
const ICEBUG_WORKER_SOURCE = `
|
|
360
|
+
const { parentPort, workerData } = require('node:worker_threads');
|
|
361
|
+
|
|
362
|
+
const isNumericArrayLike = (value) =>
|
|
363
|
+
typeof value === 'object' &&
|
|
364
|
+
value !== null &&
|
|
365
|
+
'length' in value &&
|
|
366
|
+
typeof value.length === 'number';
|
|
367
|
+
|
|
368
|
+
const readPartition = (runner) => {
|
|
369
|
+
const candidates = [
|
|
370
|
+
typeof runner.getPartition === 'function' ? runner.getPartition() : runner.partition,
|
|
371
|
+
typeof runner.getCommunities === 'function' ? runner.getCommunities() : undefined,
|
|
372
|
+
typeof runner.getMembership === 'function' ? runner.getMembership() : undefined,
|
|
373
|
+
typeof runner.getMemberships === 'function' ? runner.getMemberships() : undefined,
|
|
374
|
+
];
|
|
375
|
+
|
|
376
|
+
for (const candidate of candidates) {
|
|
377
|
+
if (isNumericArrayLike(candidate)) {
|
|
378
|
+
return Array.from(candidate, Number);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
throw new Error('optional icebug ParallelLeidenView did not expose a partition array');
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const readModularity = (runner) => {
|
|
386
|
+
if (typeof runner.getModularity === 'function') return runner.getModularity();
|
|
387
|
+
if (typeof runner.modularity === 'function') return runner.modularity();
|
|
388
|
+
if (typeof runner.modularity === 'number') return runner.modularity;
|
|
389
|
+
return 0;
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
(async () => {
|
|
393
|
+
const imported = await import('icebug');
|
|
394
|
+
const icebug = imported.default ?? imported;
|
|
395
|
+
const fromCSR = icebug.Graph?.fromCSR;
|
|
396
|
+
const ParallelLeidenView = icebug.community?.ParallelLeidenView;
|
|
397
|
+
if (!fromCSR || !ParallelLeidenView) {
|
|
398
|
+
throw new Error('optional icebug module does not expose Graph.fromCSR/ParallelLeidenView');
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (typeof icebug.setNumberOfThreads !== 'function' || typeof icebug.setSeed !== 'function') {
|
|
402
|
+
throw new Error('optional icebug module does not expose deterministic thread/seed controls');
|
|
403
|
+
}
|
|
404
|
+
icebug.setNumberOfThreads(workerData.threads);
|
|
405
|
+
icebug.setSeed(workerData.seed, false);
|
|
406
|
+
|
|
407
|
+
const nativeGraph = fromCSR(workerData.nodeCount, false, workerData.indices, workerData.indptr);
|
|
408
|
+
let runner;
|
|
409
|
+
try {
|
|
410
|
+
runner = new ParallelLeidenView(nativeGraph, {
|
|
411
|
+
iterations: workerData.iterations,
|
|
412
|
+
gamma: workerData.gamma,
|
|
413
|
+
randomize: workerData.randomize,
|
|
414
|
+
});
|
|
415
|
+
} catch {
|
|
416
|
+
runner = new ParallelLeidenView(
|
|
417
|
+
nativeGraph,
|
|
418
|
+
workerData.iterations,
|
|
419
|
+
workerData.gamma,
|
|
420
|
+
workerData.randomize,
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (typeof runner.run !== 'function') {
|
|
425
|
+
throw new Error('optional icebug ParallelLeidenView does not expose run()');
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
runner.run();
|
|
429
|
+
parentPort.postMessage({
|
|
430
|
+
ok: true,
|
|
431
|
+
partition: readPartition(runner),
|
|
432
|
+
modularity: readModularity(runner),
|
|
433
|
+
});
|
|
434
|
+
})().catch((error) => {
|
|
435
|
+
parentPort.postMessage({ ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
436
|
+
});
|
|
437
|
+
`;
|
|
438
|
+
const normalizePartition = (projection, partition) => {
|
|
439
|
+
const remap = new Map();
|
|
440
|
+
const communities = {};
|
|
441
|
+
for (let index = 0; index < projection.nodes.length; index++) {
|
|
442
|
+
const rawCommunity = String(partition[index]);
|
|
443
|
+
let communityId = remap.get(rawCommunity);
|
|
444
|
+
if (communityId === undefined) {
|
|
445
|
+
communityId = remap.size;
|
|
446
|
+
remap.set(rawCommunity, communityId);
|
|
447
|
+
}
|
|
448
|
+
communities[projection.nodes[index].id] = communityId;
|
|
449
|
+
}
|
|
450
|
+
return communities;
|
|
451
|
+
};
|
|
199
452
|
// ============================================================================
|
|
200
453
|
// HELPER: Create community nodes with heuristic labels
|
|
201
454
|
// ============================================================================
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
* ancestor scope, and if THAT produces nothing either the edge is
|
|
45
45
|
* skipped (with a count returned in `EmitStats.skippedNoCaller`).
|
|
46
46
|
*/
|
|
47
|
+
import { toZeroBasedLine } from './utils/line-base.js';
|
|
47
48
|
/**
|
|
48
49
|
* Drain `referenceIndex.bySourceScope` into graph edges.
|
|
49
50
|
*
|
|
@@ -95,8 +96,8 @@ export function emitScopeGraph(input) {
|
|
|
95
96
|
properties: {
|
|
96
97
|
name: scope.kind,
|
|
97
98
|
filePath: scope.filePath,
|
|
98
|
-
startLine: scope.range.startLine,
|
|
99
|
-
endLine: scope.range.endLine,
|
|
99
|
+
startLine: toZeroBasedLine(scope.range.startLine),
|
|
100
|
+
endLine: toZeroBasedLine(scope.range.endLine),
|
|
100
101
|
description: `Scope: ${scope.kind}`,
|
|
101
102
|
},
|
|
102
103
|
});
|