@ophan/core 0.0.1 → 0.0.3
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 -93
- package/dist/community-detectors/index.d.ts +20 -0
- package/dist/community-detectors/index.d.ts.map +1 -0
- package/dist/community-detectors/index.js +45 -0
- package/dist/community-detectors/label-prop.d.ts +20 -0
- package/dist/community-detectors/label-prop.d.ts.map +1 -0
- package/dist/community-detectors/label-prop.js +77 -0
- package/dist/community-detectors/leiden.d.ts +22 -0
- package/dist/community-detectors/leiden.d.ts.map +1 -0
- package/dist/community-detectors/leiden.js +312 -0
- package/dist/community-detectors/louvain.d.ts +13 -0
- package/dist/community-detectors/louvain.d.ts.map +1 -0
- package/dist/community-detectors/louvain.js +29 -0
- package/dist/community-detectors/types.d.ts +36 -0
- package/dist/community-detectors/types.d.ts.map +1 -0
- package/dist/{parsers/__fixtures__/no-functions.js → community-detectors/types.js} +0 -2
- package/dist/edge-resolvers/call.d.ts +13 -0
- package/dist/edge-resolvers/call.d.ts.map +1 -0
- package/dist/edge-resolvers/call.js +40 -0
- package/dist/edge-resolvers/co-location.d.ts +16 -0
- package/dist/edge-resolvers/co-location.d.ts.map +1 -0
- package/dist/edge-resolvers/co-location.js +129 -0
- package/dist/edge-resolvers/import.d.ts +16 -0
- package/dist/edge-resolvers/import.d.ts.map +1 -0
- package/dist/edge-resolvers/import.js +118 -0
- package/dist/edge-resolvers/index.d.ts +9 -0
- package/dist/edge-resolvers/index.d.ts.map +1 -0
- package/dist/edge-resolvers/index.js +29 -0
- package/dist/edge-resolvers/jsx-ref.d.ts +13 -0
- package/dist/edge-resolvers/jsx-ref.d.ts.map +1 -0
- package/dist/edge-resolvers/jsx-ref.js +40 -0
- package/dist/edge-resolvers/types.d.ts +40 -0
- package/dist/edge-resolvers/types.d.ts.map +1 -0
- package/dist/edge-resolvers/types.js +2 -0
- package/dist/graph.d.ts +293 -0
- package/dist/graph.d.ts.map +1 -0
- package/dist/graph.js +1295 -0
- package/dist/index.d.ts +37 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +385 -183
- package/dist/migrations.d.ts +25 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +323 -0
- package/dist/module-resolvers/index.d.ts +11 -0
- package/dist/module-resolvers/index.d.ts.map +1 -0
- package/dist/module-resolvers/index.js +67 -0
- package/dist/module-resolvers/javascript.d.ts +18 -0
- package/dist/module-resolvers/javascript.d.ts.map +1 -0
- package/dist/module-resolvers/javascript.js +130 -0
- package/dist/module-resolvers/types.d.ts +18 -0
- package/dist/module-resolvers/types.d.ts.map +1 -0
- package/dist/module-resolvers/types.js +2 -0
- package/dist/parsers/python.d.ts.map +1 -1
- package/dist/parsers/python.js +38 -4
- package/dist/parsers/typescript.d.ts.map +1 -1
- package/dist/parsers/typescript.js +133 -0
- package/dist/practices.d.ts +28 -0
- package/dist/practices.d.ts.map +1 -0
- package/dist/practices.js +95 -0
- package/dist/schemas.d.ts +251 -3
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +121 -6
- package/dist/shared.d.ts +8 -0
- package/dist/shared.d.ts.map +1 -1
- package/dist/summarize.d.ts +165 -0
- package/dist/summarize.d.ts.map +1 -0
- package/dist/summarize.js +1067 -0
- package/ophan_logo.png +0 -0
- package/package.json +11 -2
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -492
- package/dist/parsers/__fixtures__/arrow-functions.d.ts +0 -5
- package/dist/parsers/__fixtures__/arrow-functions.d.ts.map +0 -1
- package/dist/parsers/__fixtures__/arrow-functions.js +0 -16
- package/dist/parsers/__fixtures__/class-methods.d.ts +0 -6
- package/dist/parsers/__fixtures__/class-methods.d.ts.map +0 -1
- package/dist/parsers/__fixtures__/class-methods.js +0 -12
- package/dist/parsers/__fixtures__/no-functions.d.ts +0 -9
- package/dist/parsers/__fixtures__/no-functions.d.ts.map +0 -1
- package/dist/parsers/python.test.d.ts +0 -2
- package/dist/parsers/python.test.d.ts.map +0 -1
- package/dist/parsers/python.test.js +0 -96
- package/dist/parsers/typescript.test.d.ts +0 -2
- package/dist/parsers/typescript.test.d.ts.map +0 -1
- package/dist/parsers/typescript.test.js +0 -106
- package/dist/test-utils.d.ts +0 -46
- package/dist/test-utils.d.ts.map +0 -1
- package/dist/test-utils.js +0 -141
package/README.md
CHANGED
|
@@ -1,107 +1,70 @@
|
|
|
1
1
|
# @ophan/core
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
### Content-Addressed Storage
|
|
15
|
-
Analysis is keyed by SHA256 of function source code:
|
|
16
|
-
- Same function on any branch = same hash = same analysis entry
|
|
17
|
-
- Skip analysis entirely when hash already exists in DB
|
|
18
|
-
- Insert-only sync — no conflict resolution needed
|
|
19
|
-
- Orphaned hashes accumulate harmlessly until manual `ophan gc` (30-day grace period, safe for branch switching)
|
|
20
|
-
|
|
21
|
-
### Two-Table Schema
|
|
22
|
-
- `function_analysis` — `content_hash → analysis JSON, model_version, created_at, language, entity_type` (synced to cloud)
|
|
23
|
-
- `file_functions` — `file_path + function_name → content_hash, file_mtime, language, entity_type` (local only). No line numbers — resolved at runtime via LSP/parser.
|
|
24
|
-
|
|
25
|
-
### Incremental Scanning
|
|
26
|
-
1. Check `file_mtime` against stored value — skip unchanged files entirely (no parse)
|
|
27
|
-
2. Re-parse changed files, extract functions, compute hashes
|
|
28
|
-
3. Look up hash in `function_analysis` — skip if exists
|
|
29
|
-
4. Only call Claude API for functions with new/unknown hashes
|
|
30
|
-
5. Update `file_functions` with file path, function name, content_hash, mtime
|
|
31
|
-
|
|
32
|
-
### AI Analysis
|
|
33
|
-
Leverages Claude to provide:
|
|
34
|
-
- Natural language function descriptions
|
|
35
|
-
- Parameter and return type documentation
|
|
36
|
-
- Security vulnerability identification
|
|
37
|
-
- Data flow classification
|
|
3
|
+
The analysis engine behind [Ophan](https://ophan.dev) — AI-powered security analysis and documentation for codebases.
|
|
4
|
+
|
|
5
|
+
This package provides the core analysis pipeline: source code parsing, function extraction, Claude-powered security analysis, and local SQLite storage. Used by [`@ophan/cli`](https://www.npmjs.com/package/@ophan/cli) and the [Ophan VS Code extension](https://marketplace.visualstudio.com/items?itemName=ophan.ophan).
|
|
6
|
+
|
|
7
|
+
## What It Does
|
|
8
|
+
|
|
9
|
+
- **Parses source code** using language-native ASTs (TypeScript compiler API, Python's `ast` module)
|
|
10
|
+
- **Extracts functions** and computes SHA256 content hashes for change detection
|
|
11
|
+
- **Analyzes with Claude** to detect security vulnerabilities and generate documentation
|
|
12
|
+
- **Stores results** in a local SQLite database, keyed by content hash
|
|
38
13
|
|
|
39
14
|
### Security Detection
|
|
40
|
-
Identifies common vulnerabilities:
|
|
41
|
-
- SQL injection risks
|
|
42
|
-
- Cross-site scripting (XSS) potential
|
|
43
|
-
- Hardcoded secrets and credentials
|
|
44
|
-
- Unsanitized user input
|
|
45
|
-
- Path traversal vulnerabilities
|
|
46
|
-
|
|
47
|
-
### Data Flow Tagging
|
|
48
|
-
Classifies functions by data they handle:
|
|
49
|
-
- `user_input` — Processes user-provided data
|
|
50
|
-
- `pii` — Handles personally identifiable information
|
|
51
|
-
- `credentials` — Works with authentication data
|
|
52
|
-
- `database` — Database read/write operations
|
|
53
|
-
- `external_api` — Makes external HTTP requests
|
|
54
|
-
- `file_system` — File I/O operations
|
|
55
|
-
- `config` — Configuration management
|
|
56
|
-
- `internal` — Internal system operations
|
|
57
|
-
|
|
58
|
-
## Storage
|
|
59
|
-
|
|
60
|
-
SQLite database at `.ophan/index.db` (gitignored, per-repo). Designed for:
|
|
61
|
-
- Fast querying by content hash, file path, and line number
|
|
62
|
-
- Incremental updates (only new hashes trigger analysis)
|
|
63
|
-
- JSON storage for structured analysis results
|
|
64
|
-
- Branch-agnostic analysis with branch-specific location mappings
|
|
65
|
-
|
|
66
|
-
## Multi-Language Architecture
|
|
67
|
-
|
|
68
|
-
### Parser Interface (`src/parsers/`)
|
|
69
|
-
|
|
70
|
-
Function extraction is handled by pluggable, language-specific parsers. Each implements the `LanguageParser` interface:
|
|
71
15
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
16
|
+
Identifies vulnerabilities including SQL injection, XSS, hardcoded secrets, path traversal, insecure deserialization, and unsanitized user input.
|
|
17
|
+
|
|
18
|
+
### Data Flow Classification
|
|
19
|
+
|
|
20
|
+
Tags functions by the data they handle: user input, PII, credentials, database operations, external APIs, file system access, and more.
|
|
21
|
+
|
|
22
|
+
### Auto-Documentation
|
|
23
|
+
|
|
24
|
+
Generates plain-English descriptions, parameter documentation, and return type documentation for every function.
|
|
79
25
|
|
|
80
|
-
|
|
81
|
-
- **TypeScript/JavaScript** (`parsers/typescript.ts`): Uses the TypeScript compiler API. Handles `.ts`, `.tsx`, `.js`, `.jsx`.
|
|
82
|
-
- **Python** (`parsers/python.ts`): Shells out to `python3` with an inline `ast` module script. Handles `.py`. Gracefully skips if `python3` not installed.
|
|
26
|
+
## Supported Languages
|
|
83
27
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
28
|
+
| Language | Parser | Extensions |
|
|
29
|
+
|----------|--------|------------|
|
|
30
|
+
| TypeScript | TypeScript compiler API | `.ts`, `.tsx` |
|
|
31
|
+
| JavaScript | TypeScript compiler API | `.js`, `.jsx` |
|
|
32
|
+
| Python | Python `ast` module | `.py` |
|
|
88
33
|
|
|
89
|
-
|
|
34
|
+
## Usage
|
|
90
35
|
|
|
91
|
-
|
|
36
|
+
Most users should use [`@ophan/cli`](https://www.npmjs.com/package/@ophan/cli) or the [VS Code extension](https://marketplace.visualstudio.com/items?itemName=ophan.ophan) instead of importing this package directly.
|
|
92
37
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
38
|
+
`@ophan/core` is published for tools that need to build on the Ophan analysis pipeline programmatically.
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { analyzeRepository, initDb } from '@ophan/core';
|
|
42
|
+
|
|
43
|
+
const db = initDb('/path/to/repo');
|
|
44
|
+
await analyzeRepository(db, '/path/to/repo', {
|
|
45
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Schemas
|
|
50
|
+
|
|
51
|
+
Zod schemas and TypeScript types for Ophan's analysis data are available as a lightweight subpath import — no native dependencies required:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import {
|
|
55
|
+
ClaudeAnalysisResponse,
|
|
56
|
+
SECURITY_FLAG_LABELS,
|
|
57
|
+
DATA_TAG_LABELS,
|
|
58
|
+
} from '@ophan/core/schemas';
|
|
59
|
+
```
|
|
96
60
|
|
|
97
|
-
|
|
61
|
+
## Resources
|
|
98
62
|
|
|
99
|
-
|
|
63
|
+
- [Documentation](https://docs.ophan.dev)
|
|
64
|
+
- [CLI Package](https://www.npmjs.com/package/@ophan/cli)
|
|
65
|
+
- [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=ophan.ophan)
|
|
66
|
+
- [GitHub](https://github.com/nicholasgriffintn/ophan)
|
|
100
67
|
|
|
101
|
-
##
|
|
68
|
+
## License
|
|
102
69
|
|
|
103
|
-
|
|
104
|
-
- Additional programming languages via parser interface (Go, Java, Rust planned)
|
|
105
|
-
- Alternative AI models (Bedrock for enterprise)
|
|
106
|
-
- Custom security rules
|
|
107
|
-
- Supabase sync for cloud features
|
|
70
|
+
MIT
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type { CommunityDetector, RawDetectionResult, DetectorOptions } from "./types";
|
|
2
|
+
export { LouvainDetector } from "./louvain";
|
|
3
|
+
export { LabelPropDetector } from "./label-prop";
|
|
4
|
+
export { LeidenDetector } from "./leiden";
|
|
5
|
+
import type { CommunityDetector } from "./types";
|
|
6
|
+
/**
|
|
7
|
+
* Register a community detection algorithm.
|
|
8
|
+
* Call before detectCommunities() to make the algorithm available via GraphConfig.algorithm.
|
|
9
|
+
*/
|
|
10
|
+
export declare function registerDetector(detector: CommunityDetector): void;
|
|
11
|
+
/**
|
|
12
|
+
* Get a registered detector by name.
|
|
13
|
+
* Throws if the algorithm is not registered.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getDetector(name: string): CommunityDetector;
|
|
16
|
+
/**
|
|
17
|
+
* List all registered detector names.
|
|
18
|
+
*/
|
|
19
|
+
export declare function listDetectors(): string[];
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/community-detectors/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AACtF,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAOjD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAElE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,CAO3D;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,EAAE,CAExC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LeidenDetector = exports.LabelPropDetector = exports.LouvainDetector = void 0;
|
|
4
|
+
exports.registerDetector = registerDetector;
|
|
5
|
+
exports.getDetector = getDetector;
|
|
6
|
+
exports.listDetectors = listDetectors;
|
|
7
|
+
var louvain_1 = require("./louvain");
|
|
8
|
+
Object.defineProperty(exports, "LouvainDetector", { enumerable: true, get: function () { return louvain_1.LouvainDetector; } });
|
|
9
|
+
var label_prop_1 = require("./label-prop");
|
|
10
|
+
Object.defineProperty(exports, "LabelPropDetector", { enumerable: true, get: function () { return label_prop_1.LabelPropDetector; } });
|
|
11
|
+
var leiden_1 = require("./leiden");
|
|
12
|
+
Object.defineProperty(exports, "LeidenDetector", { enumerable: true, get: function () { return leiden_1.LeidenDetector; } });
|
|
13
|
+
const louvain_2 = require("./louvain");
|
|
14
|
+
const label_prop_2 = require("./label-prop");
|
|
15
|
+
const leiden_2 = require("./leiden");
|
|
16
|
+
const registry = new Map();
|
|
17
|
+
/**
|
|
18
|
+
* Register a community detection algorithm.
|
|
19
|
+
* Call before detectCommunities() to make the algorithm available via GraphConfig.algorithm.
|
|
20
|
+
*/
|
|
21
|
+
function registerDetector(detector) {
|
|
22
|
+
registry.set(detector.name, detector);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get a registered detector by name.
|
|
26
|
+
* Throws if the algorithm is not registered.
|
|
27
|
+
*/
|
|
28
|
+
function getDetector(name) {
|
|
29
|
+
const detector = registry.get(name);
|
|
30
|
+
if (!detector) {
|
|
31
|
+
const available = [...registry.keys()].join(", ");
|
|
32
|
+
throw new Error(`Unknown community detection algorithm: "${name}". Available: ${available}`);
|
|
33
|
+
}
|
|
34
|
+
return detector;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* List all registered detector names.
|
|
38
|
+
*/
|
|
39
|
+
function listDetectors() {
|
|
40
|
+
return [...registry.keys()];
|
|
41
|
+
}
|
|
42
|
+
// Register built-in detectors
|
|
43
|
+
registerDetector(new louvain_2.LouvainDetector());
|
|
44
|
+
registerDetector(new label_prop_2.LabelPropDetector());
|
|
45
|
+
registerDetector(new leiden_2.LeidenDetector());
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type Graph from "graphology";
|
|
2
|
+
import type { CommunityDetector, DetectorOptions, RawDetectionResult } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Label Propagation Algorithm (LPA) for community detection.
|
|
5
|
+
*
|
|
6
|
+
* Each node starts with a unique label. On each iteration, every node adopts
|
|
7
|
+
* the label with the highest total edge weight among its neighbors. Ties are
|
|
8
|
+
* broken randomly. Converges when no node changes its label.
|
|
9
|
+
*
|
|
10
|
+
* Pros: fast, no resolution parameter needed, finds natural cluster boundaries
|
|
11
|
+
* Cons: non-deterministic, may produce different results on each run
|
|
12
|
+
*/
|
|
13
|
+
export declare class LabelPropDetector implements CommunityDetector {
|
|
14
|
+
readonly name = "label-propagation";
|
|
15
|
+
readonly supportsResolution = false;
|
|
16
|
+
private maxIterations;
|
|
17
|
+
constructor(maxIterations?: number);
|
|
18
|
+
detect(graph: Graph, options: DetectorOptions): RawDetectionResult;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=label-prop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"label-prop.d.ts","sourceRoot":"","sources":["../../src/community-detectors/label-prop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AACpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAEtF;;;;;;;;;GASG;AACH,qBAAa,iBAAkB,YAAW,iBAAiB;IACzD,QAAQ,CAAC,IAAI,uBAAuB;IACpC,QAAQ,CAAC,kBAAkB,SAAS;IAEpC,OAAO,CAAC,aAAa,CAAS;gBAElB,aAAa,SAAM;IAI/B,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,eAAe,GAAG,kBAAkB;CA8DnE"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LabelPropDetector = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Label Propagation Algorithm (LPA) for community detection.
|
|
6
|
+
*
|
|
7
|
+
* Each node starts with a unique label. On each iteration, every node adopts
|
|
8
|
+
* the label with the highest total edge weight among its neighbors. Ties are
|
|
9
|
+
* broken randomly. Converges when no node changes its label.
|
|
10
|
+
*
|
|
11
|
+
* Pros: fast, no resolution parameter needed, finds natural cluster boundaries
|
|
12
|
+
* Cons: non-deterministic, may produce different results on each run
|
|
13
|
+
*/
|
|
14
|
+
class LabelPropDetector {
|
|
15
|
+
constructor(maxIterations = 100) {
|
|
16
|
+
this.name = "label-propagation";
|
|
17
|
+
this.supportsResolution = false;
|
|
18
|
+
this.maxIterations = maxIterations;
|
|
19
|
+
}
|
|
20
|
+
detect(graph, options) {
|
|
21
|
+
// Initialize: each node gets a unique label
|
|
22
|
+
const labels = new Map();
|
|
23
|
+
let nextLabel = 0;
|
|
24
|
+
graph.forEachNode((node) => {
|
|
25
|
+
labels.set(node, nextLabel++);
|
|
26
|
+
});
|
|
27
|
+
const nodes = graph.nodes();
|
|
28
|
+
for (let iter = 0; iter < this.maxIterations; iter++) {
|
|
29
|
+
let changed = false;
|
|
30
|
+
// Shuffle nodes for random processing order (reduces bias)
|
|
31
|
+
for (let i = nodes.length - 1; i > 0; i--) {
|
|
32
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
33
|
+
[nodes[i], nodes[j]] = [nodes[j], nodes[i]];
|
|
34
|
+
}
|
|
35
|
+
for (const node of nodes) {
|
|
36
|
+
// Sum weights per neighboring label
|
|
37
|
+
const labelWeights = new Map();
|
|
38
|
+
graph.forEachEdge(node, (_edge, attrs, source, target) => {
|
|
39
|
+
const neighbor = source === node ? target : source;
|
|
40
|
+
const neighborLabel = labels.get(neighbor);
|
|
41
|
+
const weight = attrs[options.weightAttribute] || 1.0;
|
|
42
|
+
labelWeights.set(neighborLabel, (labelWeights.get(neighborLabel) || 0) + weight);
|
|
43
|
+
});
|
|
44
|
+
if (labelWeights.size === 0)
|
|
45
|
+
continue;
|
|
46
|
+
// Find label(s) with maximum weight
|
|
47
|
+
let maxWeight = -Infinity;
|
|
48
|
+
const candidates = [];
|
|
49
|
+
for (const [label, weight] of labelWeights) {
|
|
50
|
+
if (weight > maxWeight) {
|
|
51
|
+
maxWeight = weight;
|
|
52
|
+
candidates.length = 0;
|
|
53
|
+
candidates.push(label);
|
|
54
|
+
}
|
|
55
|
+
else if (weight === maxWeight) {
|
|
56
|
+
candidates.push(label);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Break ties randomly
|
|
60
|
+
const chosen = candidates[Math.floor(Math.random() * candidates.length)];
|
|
61
|
+
if (chosen !== labels.get(node)) {
|
|
62
|
+
labels.set(node, chosen);
|
|
63
|
+
changed = true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (!changed)
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
// Convert to Record<string, number>
|
|
70
|
+
const communities = {};
|
|
71
|
+
for (const [node, label] of labels) {
|
|
72
|
+
communities[node] = label;
|
|
73
|
+
}
|
|
74
|
+
return { communities, modularity: null };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.LabelPropDetector = LabelPropDetector;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type Graph from "graphology";
|
|
2
|
+
import type { CommunityDetector, DetectorOptions, RawDetectionResult } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Leiden community detection algorithm (Traag, van Eck & Waltman, 2019).
|
|
5
|
+
*
|
|
6
|
+
* Improvement over Louvain with three phases per iteration:
|
|
7
|
+
* 1. Local moving: nodes move to neighboring community maximizing modularity gain
|
|
8
|
+
* 2. Refinement: ensures each community is internally connected by re-examining
|
|
9
|
+
* sub-structure within communities (the key Leiden innovation)
|
|
10
|
+
* 3. Aggregation: create super-graph where communities become nodes
|
|
11
|
+
*
|
|
12
|
+
* Guarantees communities are well-connected (no disconnected subgroups within a community).
|
|
13
|
+
* Uses modularity quality function: Q = Σ_c [e_c/m - γ(n_c/2m)²]
|
|
14
|
+
*
|
|
15
|
+
* Reference: https://arxiv.org/abs/1810.08473
|
|
16
|
+
*/
|
|
17
|
+
export declare class LeidenDetector implements CommunityDetector {
|
|
18
|
+
readonly name = "leiden";
|
|
19
|
+
readonly supportsResolution = true;
|
|
20
|
+
detect(graph: Graph, options: DetectorOptions): RawDetectionResult;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=leiden.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leiden.d.ts","sourceRoot":"","sources":["../../src/community-detectors/leiden.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AACpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAEtF;;;;;;;;;;;;;GAaG;AACH,qBAAa,cAAe,YAAW,iBAAiB;IACtD,QAAQ,CAAC,IAAI,YAAY;IACzB,QAAQ,CAAC,kBAAkB,QAAQ;IAEnC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,eAAe,GAAG,kBAAkB;CAqEnE"}
|