gitnexus 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +181 -0
- package/dist/cli/ai-context.d.ts +21 -0
- package/dist/cli/ai-context.js +219 -0
- package/dist/cli/analyze.d.ts +10 -0
- package/dist/cli/analyze.js +118 -0
- package/dist/cli/clean.d.ts +8 -0
- package/dist/cli/clean.js +29 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +42 -0
- package/dist/cli/list.d.ts +6 -0
- package/dist/cli/list.js +27 -0
- package/dist/cli/mcp.d.ts +7 -0
- package/dist/cli/mcp.js +85 -0
- package/dist/cli/serve.d.ts +3 -0
- package/dist/cli/serve.js +5 -0
- package/dist/cli/status.d.ts +6 -0
- package/dist/cli/status.js +27 -0
- package/dist/config/ignore-service.d.ts +1 -0
- package/dist/config/ignore-service.js +208 -0
- package/dist/config/supported-languages.d.ts +11 -0
- package/dist/config/supported-languages.js +15 -0
- package/dist/core/embeddings/embedder.d.ts +60 -0
- package/dist/core/embeddings/embedder.js +205 -0
- package/dist/core/embeddings/embedding-pipeline.d.ts +50 -0
- package/dist/core/embeddings/embedding-pipeline.js +321 -0
- package/dist/core/embeddings/index.d.ts +9 -0
- package/dist/core/embeddings/index.js +9 -0
- package/dist/core/embeddings/text-generator.d.ts +24 -0
- package/dist/core/embeddings/text-generator.js +182 -0
- package/dist/core/embeddings/types.d.ts +87 -0
- package/dist/core/embeddings/types.js +32 -0
- package/dist/core/graph/graph.d.ts +2 -0
- package/dist/core/graph/graph.js +61 -0
- package/dist/core/graph/types.d.ts +50 -0
- package/dist/core/graph/types.js +1 -0
- package/dist/core/ingestion/ast-cache.d.ts +11 -0
- package/dist/core/ingestion/ast-cache.js +34 -0
- package/dist/core/ingestion/call-processor.d.ts +8 -0
- package/dist/core/ingestion/call-processor.js +269 -0
- package/dist/core/ingestion/cluster-enricher.d.ts +38 -0
- package/dist/core/ingestion/cluster-enricher.js +170 -0
- package/dist/core/ingestion/community-processor.d.ts +39 -0
- package/dist/core/ingestion/community-processor.js +269 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +39 -0
- package/dist/core/ingestion/entry-point-scoring.js +235 -0
- package/dist/core/ingestion/filesystem-walker.d.ts +5 -0
- package/dist/core/ingestion/filesystem-walker.js +26 -0
- package/dist/core/ingestion/framework-detection.d.ts +38 -0
- package/dist/core/ingestion/framework-detection.js +183 -0
- package/dist/core/ingestion/heritage-processor.d.ts +14 -0
- package/dist/core/ingestion/heritage-processor.js +134 -0
- package/dist/core/ingestion/import-processor.d.ts +8 -0
- package/dist/core/ingestion/import-processor.js +490 -0
- package/dist/core/ingestion/parsing-processor.d.ts +8 -0
- package/dist/core/ingestion/parsing-processor.js +249 -0
- package/dist/core/ingestion/pipeline.d.ts +2 -0
- package/dist/core/ingestion/pipeline.js +228 -0
- package/dist/core/ingestion/process-processor.d.ts +51 -0
- package/dist/core/ingestion/process-processor.js +278 -0
- package/dist/core/ingestion/structure-processor.d.ts +2 -0
- package/dist/core/ingestion/structure-processor.js +36 -0
- package/dist/core/ingestion/symbol-table.d.ts +33 -0
- package/dist/core/ingestion/symbol-table.js +38 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +11 -0
- package/dist/core/ingestion/tree-sitter-queries.js +319 -0
- package/dist/core/ingestion/utils.d.ts +10 -0
- package/dist/core/ingestion/utils.js +44 -0
- package/dist/core/kuzu/csv-generator.d.ts +22 -0
- package/dist/core/kuzu/csv-generator.js +272 -0
- package/dist/core/kuzu/kuzu-adapter.d.ts +81 -0
- package/dist/core/kuzu/kuzu-adapter.js +568 -0
- package/dist/core/kuzu/schema.d.ts +53 -0
- package/dist/core/kuzu/schema.js +380 -0
- package/dist/core/search/bm25-index.d.ts +22 -0
- package/dist/core/search/bm25-index.js +52 -0
- package/dist/core/search/hybrid-search.d.ts +49 -0
- package/dist/core/search/hybrid-search.js +118 -0
- package/dist/core/tree-sitter/parser-loader.d.ts +4 -0
- package/dist/core/tree-sitter/parser-loader.js +42 -0
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.js +3 -0
- package/dist/mcp/core/embedder.d.ts +27 -0
- package/dist/mcp/core/embedder.js +93 -0
- package/dist/mcp/core/kuzu-adapter.d.ts +23 -0
- package/dist/mcp/core/kuzu-adapter.js +62 -0
- package/dist/mcp/local/local-backend.d.ts +73 -0
- package/dist/mcp/local/local-backend.js +752 -0
- package/dist/mcp/resources.d.ts +31 -0
- package/dist/mcp/resources.js +279 -0
- package/dist/mcp/server.d.ts +12 -0
- package/dist/mcp/server.js +130 -0
- package/dist/mcp/staleness.d.ts +15 -0
- package/dist/mcp/staleness.js +29 -0
- package/dist/mcp/tools.d.ts +24 -0
- package/dist/mcp/tools.js +160 -0
- package/dist/server/api.d.ts +6 -0
- package/dist/server/api.js +156 -0
- package/dist/storage/git.d.ts +7 -0
- package/dist/storage/git.js +39 -0
- package/dist/storage/repo-manager.d.ts +61 -0
- package/dist/storage/repo-manager.js +106 -0
- package/dist/types/pipeline.d.ts +28 -0
- package/dist/types/pipeline.js +16 -0
- package/package.json +80 -0
- package/skills/debugging.md +104 -0
- package/skills/exploring.md +112 -0
- package/skills/impact-analysis.md +114 -0
- package/skills/refactoring.md +119 -0
- package/vendor/leiden/index.cjs +355 -0
- package/vendor/leiden/utils.cjs +392 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gitnexus-exploring
|
|
3
|
+
description: Navigate unfamiliar code using GitNexus knowledge graph
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Exploring Codebases
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
```
|
|
10
|
+
0. If "Index is stale" → gitnexus_analyze({})
|
|
11
|
+
1. READ gitnexus://context → Get codebase overview (~150 tokens)
|
|
12
|
+
2. READ gitnexus://clusters → See all functional clusters
|
|
13
|
+
3. READ gitnexus://cluster/{name} → Deep dive on specific cluster
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## When to Use
|
|
17
|
+
- "How does authentication work?"
|
|
18
|
+
- "What's the project structure?"
|
|
19
|
+
- "Show me the main components"
|
|
20
|
+
- "Where is the database logic?"
|
|
21
|
+
|
|
22
|
+
## Workflow Checklist
|
|
23
|
+
```
|
|
24
|
+
Exploration Progress:
|
|
25
|
+
- [ ] READ gitnexus://context for codebase overview
|
|
26
|
+
- [ ] READ gitnexus://clusters to list all clusters
|
|
27
|
+
- [ ] Identify the relevant cluster by name
|
|
28
|
+
- [ ] READ gitnexus://cluster/{name} for cluster details
|
|
29
|
+
- [ ] Use gitnexus_explore for specific symbols
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Resource Reference
|
|
33
|
+
|
|
34
|
+
### gitnexus://context
|
|
35
|
+
Codebase overview. **Read first.**
|
|
36
|
+
```yaml
|
|
37
|
+
project: my-app
|
|
38
|
+
stats:
|
|
39
|
+
files: 42
|
|
40
|
+
symbols: 918
|
|
41
|
+
clusters: 12
|
|
42
|
+
processes: 45
|
|
43
|
+
tools_available: [search, explore, impact, overview, cypher]
|
|
44
|
+
resources_available: [clusters, processes, cluster/{name}, process/{name}]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### gitnexus://clusters
|
|
48
|
+
All functional clusters with cohesion scores.
|
|
49
|
+
```yaml
|
|
50
|
+
clusters:
|
|
51
|
+
- name: "Auth"
|
|
52
|
+
symbols: 47
|
|
53
|
+
cohesion: 92%
|
|
54
|
+
- name: "Database"
|
|
55
|
+
symbols: 32
|
|
56
|
+
cohesion: 88%
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### gitnexus://cluster/{name}
|
|
60
|
+
Members of a specific cluster.
|
|
61
|
+
```yaml
|
|
62
|
+
name: Auth
|
|
63
|
+
symbols: 47
|
|
64
|
+
cohesion: 92%
|
|
65
|
+
members:
|
|
66
|
+
- name: validateUser
|
|
67
|
+
type: Function
|
|
68
|
+
file: src/auth/validator.ts
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### gitnexus://process/{name}
|
|
72
|
+
Full execution trace.
|
|
73
|
+
```yaml
|
|
74
|
+
name: LoginFlow
|
|
75
|
+
type: cross_community
|
|
76
|
+
steps:
|
|
77
|
+
1: handleLogin (src/auth/handler.ts)
|
|
78
|
+
2: validateUser (src/auth/validator.ts)
|
|
79
|
+
3: createSession (src/auth/session.ts)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Tool Reference (When Resources Aren't Enough)
|
|
83
|
+
|
|
84
|
+
### gitnexus_explore
|
|
85
|
+
For detailed symbol context with callers/callees:
|
|
86
|
+
```
|
|
87
|
+
gitnexus_explore({name: "validateUser", type: "symbol"})
|
|
88
|
+
→ Callers: loginHandler, apiMiddleware
|
|
89
|
+
→ Callees: checkToken, getUserById
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### gitnexus_search
|
|
93
|
+
For finding code by query:
|
|
94
|
+
```
|
|
95
|
+
gitnexus_search({query: "payment validation", depth: "full"})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Example: "How does payment processing work?"
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
1. READ gitnexus://context
|
|
102
|
+
→ 918 symbols, 12 clusters
|
|
103
|
+
|
|
104
|
+
2. READ gitnexus://clusters
|
|
105
|
+
→ Clusters: Auth, Payment, Database, API...
|
|
106
|
+
|
|
107
|
+
3. READ gitnexus://cluster/Payment
|
|
108
|
+
→ Members: processPayment, validateCard, PaymentService
|
|
109
|
+
|
|
110
|
+
4. READ gitnexus://process/CheckoutFlow
|
|
111
|
+
→ handleCheckout → validateCart → processPayment → sendConfirmation
|
|
112
|
+
```
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gitnexus-impact-analysis
|
|
3
|
+
description: Analyze blast radius before making code changes
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Impact Analysis
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
```
|
|
10
|
+
0. If "Index is stale" → gitnexus_analyze({})
|
|
11
|
+
1. gitnexus_impact({target, direction: "upstream"}) → What depends on this
|
|
12
|
+
2. READ gitnexus://clusters → Check affected areas
|
|
13
|
+
3. READ gitnexus://processes → Affected execution flows
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## When to Use
|
|
17
|
+
- "Is it safe to change this function?"
|
|
18
|
+
- "What will break if I modify X?"
|
|
19
|
+
- "Show me the blast radius"
|
|
20
|
+
- "Who uses this code?"
|
|
21
|
+
|
|
22
|
+
## Understanding Output
|
|
23
|
+
|
|
24
|
+
| Depth | Risk Level | Meaning |
|
|
25
|
+
|-------|-----------|---------|
|
|
26
|
+
| d=1 | WILL BREAK | Direct callers/importers |
|
|
27
|
+
| d=2 | LIKELY AFFECTED | Indirect dependencies |
|
|
28
|
+
| d=3 | MAY NEED TESTING | Transitive effects |
|
|
29
|
+
|
|
30
|
+
## Workflow Checklist
|
|
31
|
+
```
|
|
32
|
+
Impact Analysis:
|
|
33
|
+
- [ ] gitnexus_impact(target, "upstream") to find dependents
|
|
34
|
+
- [ ] READ gitnexus://clusters to understand affected areas
|
|
35
|
+
- [ ] Check high-confidence (>0.8) dependencies first
|
|
36
|
+
- [ ] Count affected clusters (cross-cutting = higher risk)
|
|
37
|
+
- [ ] If >10 processes affected, consider splitting change
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Resource Reference
|
|
41
|
+
|
|
42
|
+
### gitnexus://clusters
|
|
43
|
+
Check which clusters might be affected:
|
|
44
|
+
```yaml
|
|
45
|
+
clusters:
|
|
46
|
+
- name: Auth
|
|
47
|
+
symbols: 47
|
|
48
|
+
- name: API
|
|
49
|
+
symbols: 32
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### gitnexus://processes
|
|
53
|
+
Find which processes touch the target:
|
|
54
|
+
```yaml
|
|
55
|
+
processes:
|
|
56
|
+
- name: LoginFlow
|
|
57
|
+
type: cross_community
|
|
58
|
+
steps: 5
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Tool Reference
|
|
62
|
+
|
|
63
|
+
### gitnexus_impact
|
|
64
|
+
Analyze blast radius:
|
|
65
|
+
```
|
|
66
|
+
gitnexus_impact({
|
|
67
|
+
target: "validateUser",
|
|
68
|
+
direction: "upstream",
|
|
69
|
+
minConfidence: 0.8,
|
|
70
|
+
maxDepth: 3
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
→ d=1 (WILL BREAK):
|
|
74
|
+
- loginHandler (src/auth/login.ts:42) [CALLS, 100%]
|
|
75
|
+
- apiMiddleware (src/api/middleware.ts:15) [CALLS, 100%]
|
|
76
|
+
|
|
77
|
+
→ d=2 (LIKELY AFFECTED):
|
|
78
|
+
- authRouter (src/routes/auth.ts:22) [CALLS, 95%]
|
|
79
|
+
|
|
80
|
+
→ Affected Processes: LoginFlow, TokenRefresh
|
|
81
|
+
→ Risk: MEDIUM (3 processes)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Risk Assessment
|
|
85
|
+
|
|
86
|
+
| Affected | Risk |
|
|
87
|
+
|----------|------|
|
|
88
|
+
| <5 symbols, 1 cluster | LOW |
|
|
89
|
+
| 5-15 symbols, 1-2 clusters | MEDIUM |
|
|
90
|
+
| >15 symbols or 3+ clusters | HIGH |
|
|
91
|
+
| Critical path (auth, payments) | CRITICAL |
|
|
92
|
+
|
|
93
|
+
## Pre-Change Checklist
|
|
94
|
+
```
|
|
95
|
+
Before Committing:
|
|
96
|
+
- [ ] Run impact analysis
|
|
97
|
+
- [ ] Review all d=1 (WILL BREAK) items
|
|
98
|
+
- [ ] Verify test coverage for affected processes
|
|
99
|
+
- [ ] If risk > MEDIUM, get code review
|
|
100
|
+
- [ ] If cross-cluster, coordinate with other teams
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Example: "What breaks if I change validateUser?"
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
1. gitnexus_impact({target: "validateUser", direction: "upstream"})
|
|
107
|
+
→ d=1: loginHandler, apiMiddleware
|
|
108
|
+
→ d=2: authRouter, sessionManager
|
|
109
|
+
|
|
110
|
+
2. READ gitnexus://clusters
|
|
111
|
+
→ Auth and API clusters affected
|
|
112
|
+
|
|
113
|
+
3. Decision: 2 direct callers, 2 clusters = MEDIUM risk
|
|
114
|
+
```
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gitnexus-refactoring
|
|
3
|
+
description: Plan safe refactors using blast radius and dependency mapping
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Refactoring with GitNexus
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
```
|
|
10
|
+
0. If "Index is stale" → gitnexus_analyze({})
|
|
11
|
+
1. gitnexus_impact({target, direction: "upstream"}) → Map all dependents
|
|
12
|
+
2. READ gitnexus://schema → Understand graph structure
|
|
13
|
+
3. gitnexus_cypher → Find all references
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## When to Use
|
|
17
|
+
- "Rename this function safely"
|
|
18
|
+
- "Extract this into a module"
|
|
19
|
+
- "Split this service"
|
|
20
|
+
- "Refactor without breaking things"
|
|
21
|
+
|
|
22
|
+
## Checklists
|
|
23
|
+
|
|
24
|
+
### Rename Symbol
|
|
25
|
+
```
|
|
26
|
+
Rename Refactoring:
|
|
27
|
+
- [ ] gitnexus_impact(oldName, "upstream") — find all callers
|
|
28
|
+
- [ ] gitnexus_search(oldName) — find string literals
|
|
29
|
+
- [ ] Check for reflection/dynamic references
|
|
30
|
+
- [ ] Update in order: interface → implementation → usages
|
|
31
|
+
- [ ] Run tests for affected processes
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Extract Module
|
|
35
|
+
```
|
|
36
|
+
Extract Module:
|
|
37
|
+
- [ ] gitnexus_explore(target, "symbol") — map dependencies
|
|
38
|
+
- [ ] gitnexus_impact(target, "upstream") — find callers
|
|
39
|
+
- [ ] READ gitnexus://cluster/{name} — check cohesion
|
|
40
|
+
- [ ] Define new module interface
|
|
41
|
+
- [ ] Update imports across affected files
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Split Function
|
|
45
|
+
```
|
|
46
|
+
Split Function:
|
|
47
|
+
- [ ] gitnexus_explore(target, "symbol") — understand callees
|
|
48
|
+
- [ ] Group related logic
|
|
49
|
+
- [ ] gitnexus_impact — verify callers won't break
|
|
50
|
+
- [ ] Create new functions
|
|
51
|
+
- [ ] Update callers
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Resource Reference
|
|
55
|
+
|
|
56
|
+
### gitnexus://schema
|
|
57
|
+
Graph structure for Cypher queries:
|
|
58
|
+
```yaml
|
|
59
|
+
nodes: [Function, Class, Method, Community, Process]
|
|
60
|
+
relationships: [CALLS, IMPORTS, EXTENDS, MEMBER_OF]
|
|
61
|
+
|
|
62
|
+
example_queries:
|
|
63
|
+
find_callers: |
|
|
64
|
+
MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "X"})
|
|
65
|
+
RETURN caller.name
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### gitnexus://cluster/{name}
|
|
69
|
+
Check if extraction preserves cohesion:
|
|
70
|
+
```yaml
|
|
71
|
+
name: Payment
|
|
72
|
+
cohesion: 92%
|
|
73
|
+
members: [processPayment, validateCard, PaymentService]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Tool Reference
|
|
77
|
+
|
|
78
|
+
### Finding all references
|
|
79
|
+
```cypher
|
|
80
|
+
MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "validateUser"})
|
|
81
|
+
RETURN caller.name, caller.filePath
|
|
82
|
+
ORDER BY caller.filePath
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Finding imports of a module
|
|
86
|
+
```cypher
|
|
87
|
+
MATCH (importer)-[:CodeRelation {type: 'IMPORTS'}]->(f:File {name: "utils.ts"})
|
|
88
|
+
RETURN importer.name, importer.filePath
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Example: Safely Rename `validateUser` to `authenticateUser`
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
1. gitnexus_impact({target: "validateUser", direction: "upstream"})
|
|
95
|
+
→ loginHandler, apiMiddleware, testUtils
|
|
96
|
+
|
|
97
|
+
2. gitnexus_search({query: "validateUser"})
|
|
98
|
+
→ Found in: config.json (dynamic reference!)
|
|
99
|
+
|
|
100
|
+
3. READ gitnexus://processes
|
|
101
|
+
→ LoginFlow, TokenRefresh, APIGateway
|
|
102
|
+
|
|
103
|
+
4. Plan update order:
|
|
104
|
+
1. Update declaration in auth.ts
|
|
105
|
+
2. Update config.json string reference
|
|
106
|
+
3. Update loginHandler
|
|
107
|
+
4. Update apiMiddleware
|
|
108
|
+
5. Run tests for LoginFlow, TokenRefresh
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Refactoring Safety Rules
|
|
112
|
+
|
|
113
|
+
| Risk Factor | Mitigation |
|
|
114
|
+
|-------------|------------|
|
|
115
|
+
| Many callers (>5) | Update in small batches |
|
|
116
|
+
| Cross-cluster | Coordinate with other teams |
|
|
117
|
+
| String references | Search for dynamic usage |
|
|
118
|
+
| Reflection | Check for dynamic invocation |
|
|
119
|
+
| External exports | May break downstream repos |
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graphology Leiden Algorithm
|
|
3
|
+
* ============================
|
|
4
|
+
*
|
|
5
|
+
* JavaScript implementation of the Leiden community detection
|
|
6
|
+
* algorithm for graphology.
|
|
7
|
+
*
|
|
8
|
+
* Vendored from: https://github.com/graphology/graphology/tree/master/src/communities-leiden
|
|
9
|
+
* License: MIT
|
|
10
|
+
*
|
|
11
|
+
* [Reference]
|
|
12
|
+
* Traag, V. A., et al. "From Louvain to Leiden: Guaranteeing Well-Connected
|
|
13
|
+
* Communities". Scientific Reports, vol. 9, no 1, 2019, p. 5233.
|
|
14
|
+
* https://arxiv.org/abs/1810.08473
|
|
15
|
+
*/
|
|
16
|
+
var resolveDefaults = require('graphology-utils/defaults');
|
|
17
|
+
var isGraph = require('graphology-utils/is-graph');
|
|
18
|
+
var inferType = require('graphology-utils/infer-type');
|
|
19
|
+
var SparseMap = require('mnemonist/sparse-map');
|
|
20
|
+
var SparseQueueSet = require('mnemonist/sparse-queue-set');
|
|
21
|
+
var createRandomIndex = require('pandemonium/random-index').createRandomIndex;
|
|
22
|
+
var utils = require('./utils.cjs');
|
|
23
|
+
|
|
24
|
+
var indices = require('graphology-indices/louvain');
|
|
25
|
+
var addWeightToCommunity = utils.addWeightToCommunity;
|
|
26
|
+
|
|
27
|
+
var UndirectedLouvainIndex = indices.UndirectedLouvainIndex;
|
|
28
|
+
|
|
29
|
+
var UndirectedLeidenAddenda = utils.UndirectedLeidenAddenda;
|
|
30
|
+
|
|
31
|
+
var DEFAULTS = {
|
|
32
|
+
attributes: {
|
|
33
|
+
community: 'community',
|
|
34
|
+
weight: 'weight'
|
|
35
|
+
},
|
|
36
|
+
randomness: 0.01,
|
|
37
|
+
randomWalk: true,
|
|
38
|
+
resolution: 1,
|
|
39
|
+
rng: Math.random,
|
|
40
|
+
weighted: false
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
var EPSILON = 1e-10;
|
|
44
|
+
|
|
45
|
+
function tieBreaker(
|
|
46
|
+
bestCommunity,
|
|
47
|
+
currentCommunity,
|
|
48
|
+
targetCommunity,
|
|
49
|
+
delta,
|
|
50
|
+
bestDelta
|
|
51
|
+
) {
|
|
52
|
+
if (Math.abs(delta - bestDelta) < EPSILON) {
|
|
53
|
+
if (bestCommunity === currentCommunity) {
|
|
54
|
+
return false;
|
|
55
|
+
} else {
|
|
56
|
+
return targetCommunity > bestCommunity;
|
|
57
|
+
}
|
|
58
|
+
} else if (delta > bestDelta) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function undirectedLeiden(detailed, graph, options) {
|
|
66
|
+
var index = new UndirectedLouvainIndex(graph, {
|
|
67
|
+
attributes: {
|
|
68
|
+
weight: options.attributes.weight
|
|
69
|
+
},
|
|
70
|
+
keepDendrogram: detailed,
|
|
71
|
+
resolution: options.resolution,
|
|
72
|
+
weighted: options.weighted
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
var addenda = new UndirectedLeidenAddenda(index, {
|
|
76
|
+
randomness: options.randomness,
|
|
77
|
+
rng: options.rng
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
var randomIndex = createRandomIndex(options.rng);
|
|
81
|
+
|
|
82
|
+
// Communities
|
|
83
|
+
var currentCommunity, targetCommunity;
|
|
84
|
+
var communities = new SparseMap(Float64Array, index.C);
|
|
85
|
+
|
|
86
|
+
// Traversal
|
|
87
|
+
var queue = new SparseQueueSet(index.C),
|
|
88
|
+
start,
|
|
89
|
+
end,
|
|
90
|
+
weight,
|
|
91
|
+
ci,
|
|
92
|
+
ri,
|
|
93
|
+
s,
|
|
94
|
+
i,
|
|
95
|
+
j,
|
|
96
|
+
l;
|
|
97
|
+
|
|
98
|
+
// Metrics
|
|
99
|
+
var degree, targetCommunityDegree;
|
|
100
|
+
|
|
101
|
+
// Moves
|
|
102
|
+
var bestCommunity, bestDelta, deltaIsBetter, delta;
|
|
103
|
+
|
|
104
|
+
// Details
|
|
105
|
+
var deltaComputations = 0,
|
|
106
|
+
nodesVisited = 0,
|
|
107
|
+
moves = [],
|
|
108
|
+
currentMoves;
|
|
109
|
+
|
|
110
|
+
while (true) {
|
|
111
|
+
l = index.C;
|
|
112
|
+
|
|
113
|
+
currentMoves = 0;
|
|
114
|
+
|
|
115
|
+
// Traversal of the graph
|
|
116
|
+
ri = options.randomWalk ? randomIndex(l) : 0;
|
|
117
|
+
|
|
118
|
+
for (s = 0; s < l; s++, ri++) {
|
|
119
|
+
i = ri % l;
|
|
120
|
+
queue.enqueue(i);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
while (queue.size !== 0) {
|
|
124
|
+
i = queue.dequeue();
|
|
125
|
+
nodesVisited++;
|
|
126
|
+
|
|
127
|
+
degree = 0;
|
|
128
|
+
communities.clear();
|
|
129
|
+
|
|
130
|
+
currentCommunity = index.belongings[i];
|
|
131
|
+
|
|
132
|
+
start = index.starts[i];
|
|
133
|
+
end = index.starts[i + 1];
|
|
134
|
+
|
|
135
|
+
// Traversing neighbors
|
|
136
|
+
for (; start < end; start++) {
|
|
137
|
+
j = index.neighborhood[start];
|
|
138
|
+
weight = index.weights[start];
|
|
139
|
+
|
|
140
|
+
targetCommunity = index.belongings[j];
|
|
141
|
+
|
|
142
|
+
// Incrementing metrics
|
|
143
|
+
degree += weight;
|
|
144
|
+
addWeightToCommunity(communities, targetCommunity, weight);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Finding best community to move to
|
|
148
|
+
bestDelta = index.fastDeltaWithOwnCommunity(
|
|
149
|
+
i,
|
|
150
|
+
degree,
|
|
151
|
+
communities.get(currentCommunity) || 0,
|
|
152
|
+
currentCommunity
|
|
153
|
+
);
|
|
154
|
+
bestCommunity = currentCommunity;
|
|
155
|
+
|
|
156
|
+
for (ci = 0; ci < communities.size; ci++) {
|
|
157
|
+
targetCommunity = communities.dense[ci];
|
|
158
|
+
|
|
159
|
+
if (targetCommunity === currentCommunity) continue;
|
|
160
|
+
|
|
161
|
+
targetCommunityDegree = communities.vals[ci];
|
|
162
|
+
|
|
163
|
+
deltaComputations++;
|
|
164
|
+
|
|
165
|
+
delta = index.fastDelta(
|
|
166
|
+
i,
|
|
167
|
+
degree,
|
|
168
|
+
targetCommunityDegree,
|
|
169
|
+
targetCommunity
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
deltaIsBetter = tieBreaker(
|
|
173
|
+
bestCommunity,
|
|
174
|
+
currentCommunity,
|
|
175
|
+
targetCommunity,
|
|
176
|
+
delta,
|
|
177
|
+
bestDelta
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
if (deltaIsBetter) {
|
|
181
|
+
bestDelta = delta;
|
|
182
|
+
bestCommunity = targetCommunity;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (bestDelta < 0) {
|
|
187
|
+
bestCommunity = index.isolate(i, degree);
|
|
188
|
+
|
|
189
|
+
if (bestCommunity === currentCommunity) continue;
|
|
190
|
+
} else {
|
|
191
|
+
if (bestCommunity === currentCommunity) {
|
|
192
|
+
continue;
|
|
193
|
+
} else {
|
|
194
|
+
index.move(i, degree, bestCommunity);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
currentMoves++;
|
|
199
|
+
|
|
200
|
+
// Adding neighbors from other communities to the queue
|
|
201
|
+
start = index.starts[i];
|
|
202
|
+
end = index.starts[i + 1];
|
|
203
|
+
|
|
204
|
+
for (; start < end; start++) {
|
|
205
|
+
j = index.neighborhood[start];
|
|
206
|
+
targetCommunity = index.belongings[j];
|
|
207
|
+
|
|
208
|
+
if (targetCommunity !== bestCommunity) queue.enqueue(j);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
moves.push(currentMoves);
|
|
213
|
+
|
|
214
|
+
if (currentMoves === 0) {
|
|
215
|
+
index.zoomOut();
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (!addenda.onlySingletons()) {
|
|
220
|
+
// We continue working on the induced graph
|
|
221
|
+
addenda.zoomOut();
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
var results = {
|
|
229
|
+
index: index,
|
|
230
|
+
deltaComputations: deltaComputations,
|
|
231
|
+
nodesVisited: nodesVisited,
|
|
232
|
+
moves: moves
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
return results;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Function returning the communities mapping of the graph.
|
|
240
|
+
*
|
|
241
|
+
* @param {boolean} assign - Assign communities to nodes attributes?
|
|
242
|
+
* @param {boolean} detailed - Whether to return detailed information.
|
|
243
|
+
* @param {Graph} graph - Target graph.
|
|
244
|
+
* @param {object} options - Options:
|
|
245
|
+
* @param {object} attributes - Attribute names:
|
|
246
|
+
* @param {string} community - Community node attribute name.
|
|
247
|
+
* @param {string} weight - Weight edge attribute name.
|
|
248
|
+
* @param {number} randomness - Randomness parameter.
|
|
249
|
+
* @param {boolean} randomWalk - Whether to traverse the graph in random order.
|
|
250
|
+
* @param {number} resolution - Resolution parameter.
|
|
251
|
+
* @param {function} rng - RNG function to use.
|
|
252
|
+
* @param {boolean} weighted - Whether to compute the weighted version.
|
|
253
|
+
* @return {object}
|
|
254
|
+
*/
|
|
255
|
+
function leiden(assign, detailed, graph, options) {
|
|
256
|
+
if (!isGraph(graph))
|
|
257
|
+
throw new Error(
|
|
258
|
+
'graphology-communities-leiden: the given graph is not a valid graphology instance.'
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
var type = inferType(graph);
|
|
262
|
+
|
|
263
|
+
if (type === 'mixed')
|
|
264
|
+
throw new Error(
|
|
265
|
+
'graphology-communities-leiden: cannot run the algorithm on a true mixed graph.'
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
if (type === 'directed')
|
|
269
|
+
throw new Error(
|
|
270
|
+
'graphology-communities-leiden: not yet implemented for directed graphs.'
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
// Attributes name
|
|
274
|
+
options = resolveDefaults(options, DEFAULTS);
|
|
275
|
+
|
|
276
|
+
// Empty graph case
|
|
277
|
+
var c = 0;
|
|
278
|
+
|
|
279
|
+
if (graph.size === 0) {
|
|
280
|
+
if (assign) {
|
|
281
|
+
graph.forEachNode(function (node) {
|
|
282
|
+
graph.setNodeAttribute(node, options.attributes.communities, c++);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
var communities = {};
|
|
289
|
+
|
|
290
|
+
graph.forEachNode(function (node) {
|
|
291
|
+
communities[node] = c++;
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
if (!detailed) return communities;
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
communities: communities,
|
|
298
|
+
count: graph.order,
|
|
299
|
+
deltaComputations: 0,
|
|
300
|
+
dendrogram: null,
|
|
301
|
+
level: 0,
|
|
302
|
+
modularity: NaN,
|
|
303
|
+
moves: null,
|
|
304
|
+
nodesVisited: 0,
|
|
305
|
+
resolution: options.resolution
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
var fn = undirectedLeiden;
|
|
310
|
+
|
|
311
|
+
var results = fn(detailed, graph, options);
|
|
312
|
+
|
|
313
|
+
var index = results.index;
|
|
314
|
+
|
|
315
|
+
// Standard output
|
|
316
|
+
if (!detailed) {
|
|
317
|
+
if (assign) {
|
|
318
|
+
index.assign(options.attributes.community);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return index.collect();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Detailed output
|
|
326
|
+
var output = {
|
|
327
|
+
count: index.C,
|
|
328
|
+
deltaComputations: results.deltaComputations,
|
|
329
|
+
dendrogram: index.dendrogram,
|
|
330
|
+
level: index.level,
|
|
331
|
+
modularity: index.modularity(),
|
|
332
|
+
moves: results.moves,
|
|
333
|
+
nodesVisited: results.nodesVisited,
|
|
334
|
+
resolution: options.resolution
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
if (assign) {
|
|
338
|
+
index.assign(options.attributes.community);
|
|
339
|
+
return output;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
output.communities = index.collect();
|
|
343
|
+
|
|
344
|
+
return output;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Exporting.
|
|
349
|
+
*/
|
|
350
|
+
var fn = leiden.bind(null, false, false);
|
|
351
|
+
fn.assign = leiden.bind(null, true, false);
|
|
352
|
+
fn.detailed = leiden.bind(null, false, true);
|
|
353
|
+
fn.defaults = DEFAULTS;
|
|
354
|
+
|
|
355
|
+
module.exports = fn;
|