ts-knowledge-graph 0.1.4 → 0.1.6
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 +26 -8
- package/contribs/{web_visualisation → webview}/README.md +7 -7
- package/contribs/webview/web/css/style.css +310 -0
- package/contribs/{web_visualisation → webview}/web/index.html +40 -5
- package/contribs/{web_visualisation → webview}/web/js/app.js +378 -39
- package/contribs/{web_visualisation/web/data → webview/web/js_autogenerated}/kind_descriptions.js +2 -1
- package/contribs/{web_visualisation → webview}/web/types/app_globals.d.ts +11 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -2
- package/dist/cli.js.map +1 -1
- package/dist/cluster/cluster_weights.d.ts +20 -0
- package/dist/cluster/cluster_weights.d.ts.map +1 -0
- package/dist/cluster/cluster_weights.js +32 -0
- package/dist/cluster/cluster_weights.js.map +1 -0
- package/dist/cluster/community_detector.d.ts +61 -0
- package/dist/cluster/community_detector.d.ts.map +1 -0
- package/dist/cluster/community_detector.js +120 -0
- package/dist/cluster/community_detector.js.map +1 -0
- package/dist/cluster/community_labeler.d.ts +84 -0
- package/dist/cluster/community_labeler.d.ts.map +1 -0
- package/dist/cluster/community_labeler.js +194 -0
- package/dist/cluster/community_labeler.js.map +1 -0
- package/dist/cluster/graph_clusterer.d.ts +47 -0
- package/dist/cluster/graph_clusterer.d.ts.map +1 -0
- package/dist/cluster/graph_clusterer.js +126 -0
- package/dist/cluster/graph_clusterer.js.map +1 -0
- package/dist/commands/benchmark_command.d.ts.map +1 -1
- package/dist/commands/benchmark_command.js +13 -10
- package/dist/commands/benchmark_command.js.map +1 -1
- package/dist/commands/blast_radius_command.d.ts.map +1 -1
- package/dist/commands/blast_radius_command.js +6 -5
- package/dist/commands/blast_radius_command.js.map +1 -1
- package/dist/commands/cluster_command.d.ts +7 -0
- package/dist/commands/cluster_command.d.ts.map +1 -0
- package/dist/commands/cluster_command.js +55 -0
- package/dist/commands/cluster_command.js.map +1 -0
- package/dist/commands/command_helpers.d.ts +9 -4
- package/dist/commands/command_helpers.d.ts.map +1 -1
- package/dist/commands/command_helpers.js +13 -8
- package/dist/commands/command_helpers.js.map +1 -1
- package/dist/commands/cost_command.d.ts.map +1 -1
- package/dist/commands/cost_command.js +25 -8
- package/dist/commands/cost_command.js.map +1 -1
- package/dist/commands/enrich_command.d.ts.map +1 -1
- package/dist/commands/enrich_command.js +7 -5
- package/dist/commands/enrich_command.js.map +1 -1
- package/dist/commands/extract_command.d.ts.map +1 -1
- package/dist/commands/extract_command.js +12 -6
- package/dist/commands/extract_command.js.map +1 -1
- package/dist/commands/hotspots_command.d.ts.map +1 -1
- package/dist/commands/hotspots_command.js +6 -5
- package/dist/commands/hotspots_command.js.map +1 -1
- package/dist/commands/install_command.d.ts +15 -5
- package/dist/commands/install_command.d.ts.map +1 -1
- package/dist/commands/install_command.js +61 -23
- package/dist/commands/install_command.js.map +1 -1
- package/dist/commands/load_command.d.ts.map +1 -1
- package/dist/commands/load_command.js +18 -13
- package/dist/commands/load_command.js.map +1 -1
- package/dist/commands/neighbors_command.d.ts.map +1 -1
- package/dist/commands/neighbors_command.js +6 -5
- package/dist/commands/neighbors_command.js.map +1 -1
- package/dist/commands/references_command.d.ts.map +1 -1
- package/dist/commands/references_command.js +6 -5
- package/dist/commands/references_command.js.map +1 -1
- package/dist/commands/report_command.d.ts +16 -0
- package/dist/commands/report_command.d.ts.map +1 -0
- package/dist/commands/report_command.js +115 -0
- package/dist/commands/report_command.js.map +1 -0
- package/dist/commands/webview_command.d.ts +36 -0
- package/dist/commands/webview_command.d.ts.map +1 -0
- package/dist/commands/webview_command.js +186 -0
- package/dist/commands/webview_command.js.map +1 -0
- package/dist/enrich/cpu_profile.d.ts +33 -0
- package/dist/enrich/cpu_profile.d.ts.map +1 -1
- package/dist/enrich/cpu_profile.js +88 -0
- package/dist/enrich/cpu_profile.js.map +1 -1
- package/dist/enrich/runtime_enricher.d.ts +8 -0
- package/dist/enrich/runtime_enricher.d.ts.map +1 -1
- package/dist/enrich/runtime_enricher.js +18 -0
- package/dist/enrich/runtime_enricher.js.map +1 -1
- package/dist/enrich/runtime_join.d.ts +25 -1
- package/dist/enrich/runtime_join.d.ts.map +1 -1
- package/dist/enrich/runtime_join.js +43 -0
- package/dist/enrich/runtime_join.js.map +1 -1
- package/dist/extract/git_source.d.ts +23 -0
- package/dist/extract/git_source.d.ts.map +1 -0
- package/dist/extract/git_source.js +75 -0
- package/dist/extract/git_source.js.map +1 -0
- package/dist/query/graph_query.d.ts +36 -1
- package/dist/query/graph_query.d.ts.map +1 -1
- package/dist/query/graph_query.js +69 -6
- package/dist/query/graph_query.js.map +1 -1
- package/dist/report/graph_report.d.ts +51 -0
- package/dist/report/graph_report.d.ts.map +1 -0
- package/dist/report/graph_report.js +312 -0
- package/dist/report/graph_report.js.map +1 -0
- package/dist/report/pdf_renderer.d.ts +22 -0
- package/dist/report/pdf_renderer.d.ts.map +1 -0
- package/dist/report/pdf_renderer.js +54 -0
- package/dist/report/pdf_renderer.js.map +1 -0
- package/dist/report/report_data.d.ts +128 -0
- package/dist/report/report_data.d.ts.map +1 -0
- package/dist/report/report_data.js +191 -0
- package/dist/report/report_data.js.map +1 -0
- package/dist/schema/edge.d.ts +5 -5
- package/dist/schema/edge.d.ts.map +1 -1
- package/dist/schema/edge.js +3 -0
- package/dist/schema/edge.js.map +1 -1
- package/dist/schema/source_manifest.d.ts +30 -0
- package/dist/schema/source_manifest.d.ts.map +1 -0
- package/dist/schema/source_manifest.js +21 -0
- package/dist/schema/source_manifest.js.map +1 -0
- package/dist/store/jsonl_reader.d.ts +4 -0
- package/dist/store/jsonl_reader.d.ts.map +1 -1
- package/dist/store/jsonl_reader.js +13 -1
- package/dist/store/jsonl_reader.js.map +1 -1
- package/dist/store/jsonl_store.d.ts +2 -1
- package/dist/store/jsonl_store.d.ts.map +1 -1
- package/dist/store/jsonl_store.js +4 -1
- package/dist/store/jsonl_store.js.map +1 -1
- package/dist/store/kuzu_store.d.ts +13 -0
- package/dist/store/kuzu_store.d.ts.map +1 -1
- package/dist/store/kuzu_store.js +29 -0
- package/dist/store/kuzu_store.js.map +1 -1
- package/dist/store/output_folder.d.ts +43 -0
- package/dist/store/output_folder.d.ts.map +1 -0
- package/dist/store/output_folder.js +61 -0
- package/dist/store/output_folder.js.map +1 -0
- package/dotclaude_folder/commands/code-graph-interview.md +123 -0
- package/dotclaude_folder/commands/code-graph-optimize.md +65 -0
- package/dotclaude_folder/skills/code-graph-query/SKILL.md +4 -4
- package/package.json +72 -62
- package/contribs/web_visualisation/web/css/style.css +0 -219
- package/contribs/web_visualisation/web/tsconfig.json +0 -18
- /package/contribs/{web_visualisation/web/data → webview/web/js_autogenerated}/.gitignore +0 -0
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAElE,MAAM,OAAO,GAAG;IACf,MAAM,CAAC,GAAG,CAAC,IAAc;QACxB,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;QAC9B,OAAO;aACL,IAAI,CAAC,oBAAoB,CAAC;aAC1B,WAAW,CAAC,gEAAgE,CAAC,CAAC;QAEhF,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACjC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC9B,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAChC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACjC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC9B,eAAe,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAClC,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC/B,kBAAkB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACrC,eAAe,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAClC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC9B,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAChC,gBAAgB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACnC,kBAAkB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACrC,gBAAgB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACnC,iBAAiB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACpC,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAChC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACjC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAEjC,KAAK,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;CACD;AAED,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { EdgeKind } from '../schema/edge.js';
|
|
2
|
+
/**
|
|
3
|
+
* How strongly each edge kind pulls its two endpoints into the same community.
|
|
4
|
+
* The effective weight of an edge is this coefficient times the edge's
|
|
5
|
+
* `metadata.count` (how many times the relationship occurs in source). The
|
|
6
|
+
* exception is `CALLS_RUNTIME` — the runtime call graph from `enrich` — weighted
|
|
7
|
+
* by its normalized sample count instead, and given a slightly higher coefficient
|
|
8
|
+
* because an executed call is stronger evidence of coupling than a merely
|
|
9
|
+
* statically-possible one. It contributes only on an enriched graph; otherwise no
|
|
10
|
+
* such edges exist and it is a no-op.
|
|
11
|
+
*
|
|
12
|
+
* `IMPORTS` and `EXPORTS` are deliberately absent: they are module wiring, not
|
|
13
|
+
* coupling, the same reasoning {@link REFERENCE_EDGE_KINDS} uses. `CONTAINS`
|
|
14
|
+
* carries a low weight so same-file symbols lean together without overwhelming
|
|
15
|
+
* call structure; drop it for modules that may cross files freely. System-level
|
|
16
|
+
* kinds (`READS_CONFIG`, `CALLS_EXTERNAL`, `HANDLES`) are absent because their
|
|
17
|
+
* targets are synthesized nodes, not code symbols.
|
|
18
|
+
*/
|
|
19
|
+
export declare const CLUSTER_EDGE_WEIGHTS: Partial<Record<EdgeKind, number>>;
|
|
20
|
+
//# sourceMappingURL=cluster_weights.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cluster_weights.d.ts","sourceRoot":"","sources":["../../src/cluster/cluster_weights.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,oBAAoB,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAalE,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How strongly each edge kind pulls its two endpoints into the same community.
|
|
3
|
+
* The effective weight of an edge is this coefficient times the edge's
|
|
4
|
+
* `metadata.count` (how many times the relationship occurs in source). The
|
|
5
|
+
* exception is `CALLS_RUNTIME` — the runtime call graph from `enrich` — weighted
|
|
6
|
+
* by its normalized sample count instead, and given a slightly higher coefficient
|
|
7
|
+
* because an executed call is stronger evidence of coupling than a merely
|
|
8
|
+
* statically-possible one. It contributes only on an enriched graph; otherwise no
|
|
9
|
+
* such edges exist and it is a no-op.
|
|
10
|
+
*
|
|
11
|
+
* `IMPORTS` and `EXPORTS` are deliberately absent: they are module wiring, not
|
|
12
|
+
* coupling, the same reasoning {@link REFERENCE_EDGE_KINDS} uses. `CONTAINS`
|
|
13
|
+
* carries a low weight so same-file symbols lean together without overwhelming
|
|
14
|
+
* call structure; drop it for modules that may cross files freely. System-level
|
|
15
|
+
* kinds (`READS_CONFIG`, `CALLS_EXTERNAL`, `HANDLES`) are absent because their
|
|
16
|
+
* targets are synthesized nodes, not code symbols.
|
|
17
|
+
*/
|
|
18
|
+
export const CLUSTER_EDGE_WEIGHTS = {
|
|
19
|
+
CALLS: 3,
|
|
20
|
+
CALLS_RUNTIME: 4,
|
|
21
|
+
INSTANTIATES: 2,
|
|
22
|
+
EXTENDS: 2,
|
|
23
|
+
IMPLEMENTS: 2,
|
|
24
|
+
OVERRIDES: 1.5,
|
|
25
|
+
WRITES: 1.5,
|
|
26
|
+
READS: 1,
|
|
27
|
+
USES_TYPE: 1,
|
|
28
|
+
RETURNS: 1,
|
|
29
|
+
PARAM_TYPE: 1,
|
|
30
|
+
CONTAINS: 0.5,
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=cluster_weights.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cluster_weights.js","sourceRoot":"","sources":["../../src/cluster/cluster_weights.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAsC;IACtE,KAAK,EAAE,CAAC;IACR,aAAa,EAAE,CAAC;IAChB,YAAY,EAAE,CAAC;IACf,OAAO,EAAE,CAAC;IACV,UAAU,EAAE,CAAC;IACb,SAAS,EAAE,GAAG;IACd,MAAM,EAAE,GAAG;IACX,KAAK,EAAE,CAAC;IACR,SAAS,EAAE,CAAC;IACZ,OAAO,EAAE,CAAC;IACV,UAAU,EAAE,CAAC;IACb,QAAQ,EAAE,GAAG;CACb,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** A directed graph edge reduced to its endpoints and a pre-resolved weight. */
|
|
2
|
+
export type WeightedEdge = {
|
|
3
|
+
from: string;
|
|
4
|
+
to: string;
|
|
5
|
+
weight: number;
|
|
6
|
+
};
|
|
7
|
+
/** Tuning for a {@link CommunityDetector.detect} run. */
|
|
8
|
+
export type CommunityOptions = {
|
|
9
|
+
/** CPM resolution: higher splits the graph into more, smaller communities. */
|
|
10
|
+
resolution: number;
|
|
11
|
+
/** Leiden iterations per random start; each iteration moves, refines, and re-aggregates. */
|
|
12
|
+
iterations: number;
|
|
13
|
+
/** Independent random starts; the partition with the best CPM quality wins. */
|
|
14
|
+
randomStarts: number;
|
|
15
|
+
};
|
|
16
|
+
/** The outcome of a clustering run, keyed back to the caller's string node ids. */
|
|
17
|
+
export type CommunityResult = {
|
|
18
|
+
/** Node id to community index. Nodes touched by no weighted edge are absent. */
|
|
19
|
+
communityOf: Map<string, number>;
|
|
20
|
+
/** Number of communities found. */
|
|
21
|
+
communityCount: number;
|
|
22
|
+
/** CPM quality of the chosen partition. */
|
|
23
|
+
quality: number;
|
|
24
|
+
/** Member count per community, descending. */
|
|
25
|
+
sizes: number[];
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Defaults tuned for module-scale community detection. `resolution` is a
|
|
29
|
+
* threshold on a community's average internal edge weight; 0.1 is permissive
|
|
30
|
+
* enough to keep loosely-coupled modules together. Sweep it up (toward 1) for
|
|
31
|
+
* tighter, finer clusters.
|
|
32
|
+
*/
|
|
33
|
+
export declare const DEFAULT_COMMUNITY_OPTIONS: CommunityOptions;
|
|
34
|
+
/**
|
|
35
|
+
* Detects communities in the knowledge graph with the Leiden algorithm,
|
|
36
|
+
* delegating the optimization to `networkanalysis-ts` (the CWTS port of the
|
|
37
|
+
* library by the authors of the Leiden paper). The algorithm guarantees every
|
|
38
|
+
* community it returns is internally connected — unlike Louvain, which can leave
|
|
39
|
+
* a community split into disconnected pieces.
|
|
40
|
+
*
|
|
41
|
+
* This module is pure: it takes weighted edges and returns a partition, with no
|
|
42
|
+
* store access, mirroring how {@link RuntimeJoin} is the pure core behind
|
|
43
|
+
* `enrich`.
|
|
44
|
+
*/
|
|
45
|
+
export declare class CommunityDetector {
|
|
46
|
+
/**
|
|
47
|
+
* Runs Leiden (CPM quality function) over a weighted, undirected projection of
|
|
48
|
+
* the supplied edges.
|
|
49
|
+
*
|
|
50
|
+
* Directed edges are symmetrized: every unordered endpoint pair sums its
|
|
51
|
+
* weights onto a single undirected edge, so a mutual call counts once with the
|
|
52
|
+
* combined weight. String node ids are mapped to the contiguous integer indices
|
|
53
|
+
* the `Network` requires, and the resulting community labels are mapped back.
|
|
54
|
+
*/
|
|
55
|
+
static detect(edges: WeightedEdge[], options?: CommunityOptions): CommunityResult;
|
|
56
|
+
/** Assigns every node id touched by an edge a stable, contiguous integer index. */
|
|
57
|
+
private static indexNodes;
|
|
58
|
+
/** Collapses directed edges into undirected ones, summing weights per unordered pair and dropping self-loops. */
|
|
59
|
+
private static symmetrize;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=community_detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"community_detector.d.ts","sourceRoot":"","sources":["../../src/cluster/community_detector.ts"],"names":[],"mappings":"AAEA,gFAAgF;AAChF,MAAM,MAAM,YAAY,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,yDAAyD;AACzD,MAAM,MAAM,gBAAgB,GAAG;IAC9B,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;IACnB,4FAA4F;IAC5F,UAAU,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,YAAY,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,mFAAmF;AACnF,MAAM,MAAM,eAAe,GAAG;IAC7B,gFAAgF;IAChF,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,mCAAmC;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,KAAK,EAAE,MAAM,EAAE,CAAC;CAChB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,EAAE,gBAIvC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,qBAAa,iBAAiB;IAC7B;;;;;;;;OAQG;IACH,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,YAAY,EAAE,EAAE,OAAO,GAAE,gBAA4C,GAAG,eAAe;IAmD5G,mFAAmF;IACnF,OAAO,CAAC,MAAM,CAAC,UAAU;IAczB,iHAAiH;IACjH,OAAO,CAAC,MAAM,CAAC,UAAU;CA2BzB"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Clustering, LeidenAlgorithm, Network } from 'networkanalysis-ts';
|
|
2
|
+
/**
|
|
3
|
+
* Defaults tuned for module-scale community detection. `resolution` is a
|
|
4
|
+
* threshold on a community's average internal edge weight; 0.1 is permissive
|
|
5
|
+
* enough to keep loosely-coupled modules together. Sweep it up (toward 1) for
|
|
6
|
+
* tighter, finer clusters.
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_COMMUNITY_OPTIONS = {
|
|
9
|
+
resolution: 0.1,
|
|
10
|
+
iterations: 10,
|
|
11
|
+
randomStarts: 10,
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Detects communities in the knowledge graph with the Leiden algorithm,
|
|
15
|
+
* delegating the optimization to `networkanalysis-ts` (the CWTS port of the
|
|
16
|
+
* library by the authors of the Leiden paper). The algorithm guarantees every
|
|
17
|
+
* community it returns is internally connected — unlike Louvain, which can leave
|
|
18
|
+
* a community split into disconnected pieces.
|
|
19
|
+
*
|
|
20
|
+
* This module is pure: it takes weighted edges and returns a partition, with no
|
|
21
|
+
* store access, mirroring how {@link RuntimeJoin} is the pure core behind
|
|
22
|
+
* `enrich`.
|
|
23
|
+
*/
|
|
24
|
+
export class CommunityDetector {
|
|
25
|
+
/**
|
|
26
|
+
* Runs Leiden (CPM quality function) over a weighted, undirected projection of
|
|
27
|
+
* the supplied edges.
|
|
28
|
+
*
|
|
29
|
+
* Directed edges are symmetrized: every unordered endpoint pair sums its
|
|
30
|
+
* weights onto a single undirected edge, so a mutual call counts once with the
|
|
31
|
+
* combined weight. String node ids are mapped to the contiguous integer indices
|
|
32
|
+
* the `Network` requires, and the resulting community labels are mapped back.
|
|
33
|
+
*/
|
|
34
|
+
static detect(edges, options = DEFAULT_COMMUNITY_OPTIONS) {
|
|
35
|
+
const { ids, indexOf } = CommunityDetector.indexNodes(edges);
|
|
36
|
+
if (ids.length === 0) {
|
|
37
|
+
return { communityOf: new Map(), communityCount: 0, quality: 0, sizes: [] };
|
|
38
|
+
}
|
|
39
|
+
const undirected = CommunityDetector.symmetrize(edges, indexOf);
|
|
40
|
+
// Uniform node weights (not degree) keep this classic CPM: the resolution is
|
|
41
|
+
// then a portable threshold on a community's average internal edge weight,
|
|
42
|
+
// independent of node degree. Degree weights make the usable resolution range
|
|
43
|
+
// collapse to a graph-specific sliver.
|
|
44
|
+
const network = new Network({
|
|
45
|
+
nNodes: ids.length,
|
|
46
|
+
setNodeWeightsToTotalEdgeWeights: false,
|
|
47
|
+
edges: [undirected.sources, undirected.targets],
|
|
48
|
+
edgeWeights: undirected.weights,
|
|
49
|
+
sortedEdges: false,
|
|
50
|
+
checkIntegrity: false,
|
|
51
|
+
});
|
|
52
|
+
const algorithm = new LeidenAlgorithm();
|
|
53
|
+
algorithm.setResolution(options.resolution);
|
|
54
|
+
algorithm.setNIterations(options.iterations);
|
|
55
|
+
let best;
|
|
56
|
+
let bestQuality = Number.NEGATIVE_INFINITY;
|
|
57
|
+
for (let start = 0; start < options.randomStarts; start += 1) {
|
|
58
|
+
const clustering = new Clustering({ nNodes: network.getNNodes() });
|
|
59
|
+
algorithm.improveClustering(network, clustering);
|
|
60
|
+
const quality = algorithm.calcQuality(network, clustering);
|
|
61
|
+
if (quality > bestQuality) {
|
|
62
|
+
best = clustering;
|
|
63
|
+
bestQuality = quality;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (best === undefined) {
|
|
67
|
+
return { communityOf: new Map(), communityCount: 0, quality: 0, sizes: [] };
|
|
68
|
+
}
|
|
69
|
+
best.orderClustersByNNodes();
|
|
70
|
+
const labels = best.getClusters();
|
|
71
|
+
const communityOf = new Map();
|
|
72
|
+
ids.forEach((id, i) => communityOf.set(id, labels[i]));
|
|
73
|
+
return {
|
|
74
|
+
communityOf,
|
|
75
|
+
communityCount: best.getNClusters(),
|
|
76
|
+
quality: bestQuality,
|
|
77
|
+
sizes: best.getNNodesPerCluster(),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** Assigns every node id touched by an edge a stable, contiguous integer index. */
|
|
81
|
+
static indexNodes(edges) {
|
|
82
|
+
const indexOf = new Map();
|
|
83
|
+
const ids = [];
|
|
84
|
+
for (const edge of edges) {
|
|
85
|
+
for (const id of [edge.from, edge.to]) {
|
|
86
|
+
if (indexOf.has(id) === false) {
|
|
87
|
+
indexOf.set(id, ids.length);
|
|
88
|
+
ids.push(id);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { ids, indexOf };
|
|
93
|
+
}
|
|
94
|
+
/** Collapses directed edges into undirected ones, summing weights per unordered pair and dropping self-loops. */
|
|
95
|
+
static symmetrize(edges, indexOf) {
|
|
96
|
+
const merged = new Map();
|
|
97
|
+
for (const edge of edges) {
|
|
98
|
+
const a = indexOf.get(edge.from);
|
|
99
|
+
const b = indexOf.get(edge.to);
|
|
100
|
+
if (a === undefined || b === undefined || a === b) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const low = a < b ? a : b;
|
|
104
|
+
const high = a < b ? b : a;
|
|
105
|
+
const key = `${low}:${high}`;
|
|
106
|
+
merged.set(key, (merged.get(key) ?? 0) + edge.weight);
|
|
107
|
+
}
|
|
108
|
+
const sources = [];
|
|
109
|
+
const targets = [];
|
|
110
|
+
const weights = [];
|
|
111
|
+
for (const [key, weight] of merged) {
|
|
112
|
+
const separator = key.indexOf(':');
|
|
113
|
+
sources.push(Number(key.slice(0, separator)));
|
|
114
|
+
targets.push(Number(key.slice(separator + 1)));
|
|
115
|
+
weights.push(weight);
|
|
116
|
+
}
|
|
117
|
+
return { sources, targets, weights };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=community_detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"community_detector.js","sourceRoot":"","sources":["../../src/cluster/community_detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { WeightedEdge } from './community_detector.js';
|
|
2
|
+
/** The subset of a node's fields the labeler reasons over. */
|
|
3
|
+
export type LabelableNode = {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
kind: string;
|
|
7
|
+
filePath: string;
|
|
8
|
+
};
|
|
9
|
+
/** Everything {@link CommunityLabeler.label} needs: the partition, the nodes, and the weighted edges. */
|
|
10
|
+
export type CommunityLabelInput = {
|
|
11
|
+
/** Node id to community index, as produced by {@link CommunityDetector.detect}. */
|
|
12
|
+
communityOf: Map<string, number>;
|
|
13
|
+
/** The clustered nodes; only members present in `communityOf` are labelled. */
|
|
14
|
+
nodes: LabelableNode[];
|
|
15
|
+
/** The weighted edges the clustering ran on, used to find each community's hub. */
|
|
16
|
+
edges: WeightedEdge[];
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Derives a human-readable label for each community from its members — a
|
|
20
|
+
* deterministic, dependency-free alternative to the bare ordinal (`community 3`).
|
|
21
|
+
*
|
|
22
|
+
* The label is a composite of two signals available on every node:
|
|
23
|
+
* - the **dominant directory** the members share (code communities track module
|
|
24
|
+
* structure), and
|
|
25
|
+
* - the **hub member** — the node with the highest internal (within-community)
|
|
26
|
+
* weighted degree, i.e. the symbol the rest of the community couples to most.
|
|
27
|
+
*
|
|
28
|
+
* Because every part is derived from membership (never the ordinal index), the
|
|
29
|
+
* same group of nodes earns the same label across the algorithm's stochastic
|
|
30
|
+
* re-runs. This module is pure — it takes a partition and returns labels, with no
|
|
31
|
+
* store access — mirroring {@link CommunityDetector}.
|
|
32
|
+
*/
|
|
33
|
+
export declare class CommunityLabeler {
|
|
34
|
+
/**
|
|
35
|
+
* Returns a map from community index to a unique, human-readable label.
|
|
36
|
+
*
|
|
37
|
+
* Per community: if the members live in a single file, the label is that file's
|
|
38
|
+
* base name; otherwise it is `<directory> · <hub>` when the members concentrate
|
|
39
|
+
* in one directory, or the hub alone when they are scattered. Colliding labels
|
|
40
|
+
* are disambiguated with the hub, then the ordinal, so the result is injective.
|
|
41
|
+
*/
|
|
42
|
+
static label(input: CommunityLabelInput): Map<number, string>;
|
|
43
|
+
/** Buckets the labelled nodes by their community index, dropping nodes absent from the partition. */
|
|
44
|
+
private static groupMembers;
|
|
45
|
+
/**
|
|
46
|
+
* Sums each node's weighted degree over edges that stay inside its community.
|
|
47
|
+
* Cross-community edges and self-loops are ignored, so the result ranks members
|
|
48
|
+
* by how strongly they couple to the rest of their own community.
|
|
49
|
+
*/
|
|
50
|
+
private static internalDegree;
|
|
51
|
+
/**
|
|
52
|
+
* The name of the member with the greatest internal weighted degree, breaking
|
|
53
|
+
* ties by name then id so the choice is stable. Returns `undefined` only for an
|
|
54
|
+
* empty group.
|
|
55
|
+
*/
|
|
56
|
+
private static hubName;
|
|
57
|
+
/**
|
|
58
|
+
* A node's human-facing name. `Module` nodes carry their file path as their
|
|
59
|
+
* name, which reads poorly as a label, so they display as the file's base name.
|
|
60
|
+
*/
|
|
61
|
+
private static displayName;
|
|
62
|
+
/** Builds the un-deduplicated label for one community from its area and hub. */
|
|
63
|
+
private static composeLabel;
|
|
64
|
+
/**
|
|
65
|
+
* The directory most members share, with the fraction of members under it. Ties
|
|
66
|
+
* favour the shorter (shallower) path, then lexical order, for determinism.
|
|
67
|
+
*/
|
|
68
|
+
private static dominantDirectory;
|
|
69
|
+
/**
|
|
70
|
+
* Resolves label collisions into a unique set: a duplicated label gains its
|
|
71
|
+
* community's hub (when that adds information), and any still-colliding label
|
|
72
|
+
* gains a `#index` suffix as a last resort.
|
|
73
|
+
*/
|
|
74
|
+
private static dedupe;
|
|
75
|
+
/** Applies `rename` to every label that more than one community currently shares. */
|
|
76
|
+
private static disambiguate;
|
|
77
|
+
/** The directory portion of a POSIX-style path, or `''` for a bare file name. */
|
|
78
|
+
private static directoryOf;
|
|
79
|
+
/** The final segment of a directory path, or the whole path when it has no separator. */
|
|
80
|
+
private static lastSegment;
|
|
81
|
+
/** A file's base name without its directory or extension (`src/a/foo.ts` → `foo`). */
|
|
82
|
+
private static baseName;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=community_labeler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"community_labeler.d.ts","sourceRoot":"","sources":["../../src/cluster/community_labeler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD,8DAA8D;AAC9D,MAAM,MAAM,aAAa,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,yGAAyG;AACzG,MAAM,MAAM,mBAAmB,GAAG;IACjC,mFAAmF;IACnF,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,+EAA+E;IAC/E,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,mFAAmF;IACnF,KAAK,EAAE,YAAY,EAAE,CAAC;CACtB,CAAC;AASF;;;;;;;;;;;;;;GAcG;AACH,qBAAa,gBAAgB;IAC5B;;;;;;;OAOG;IACH,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,mBAAmB,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAc7D,qGAAqG;IACrG,OAAO,CAAC,MAAM,CAAC,YAAY;IAiB3B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;IAiB7B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,OAAO;IAkBtB;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,WAAW;IAI1B,gFAAgF;IAChF,OAAO,CAAC,MAAM,CAAC,YAAY;IAoB3B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAmBhC;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,MAAM;IAQrB,qFAAqF;IACrF,OAAO,CAAC,MAAM,CAAC,YAAY;IAe3B,iFAAiF;IACjF,OAAO,CAAC,MAAM,CAAC,WAAW;IAK1B,yFAAyF;IACzF,OAAO,CAAC,MAAM,CAAC,WAAW;IAQ1B,sFAAsF;IACtF,OAAO,CAAC,MAAM,CAAC,QAAQ;CAMvB"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fraction of a community's members that must share one directory before that
|
|
3
|
+
* directory is treated as the community's "area". Below this the members are too
|
|
4
|
+
* scattered for the directory to be meaningful, so the label falls back to the hub.
|
|
5
|
+
*/
|
|
6
|
+
const DIRECTORY_PURITY_THRESHOLD = 0.6;
|
|
7
|
+
/**
|
|
8
|
+
* Derives a human-readable label for each community from its members — a
|
|
9
|
+
* deterministic, dependency-free alternative to the bare ordinal (`community 3`).
|
|
10
|
+
*
|
|
11
|
+
* The label is a composite of two signals available on every node:
|
|
12
|
+
* - the **dominant directory** the members share (code communities track module
|
|
13
|
+
* structure), and
|
|
14
|
+
* - the **hub member** — the node with the highest internal (within-community)
|
|
15
|
+
* weighted degree, i.e. the symbol the rest of the community couples to most.
|
|
16
|
+
*
|
|
17
|
+
* Because every part is derived from membership (never the ordinal index), the
|
|
18
|
+
* same group of nodes earns the same label across the algorithm's stochastic
|
|
19
|
+
* re-runs. This module is pure — it takes a partition and returns labels, with no
|
|
20
|
+
* store access — mirroring {@link CommunityDetector}.
|
|
21
|
+
*/
|
|
22
|
+
export class CommunityLabeler {
|
|
23
|
+
/**
|
|
24
|
+
* Returns a map from community index to a unique, human-readable label.
|
|
25
|
+
*
|
|
26
|
+
* Per community: if the members live in a single file, the label is that file's
|
|
27
|
+
* base name; otherwise it is `<directory> · <hub>` when the members concentrate
|
|
28
|
+
* in one directory, or the hub alone when they are scattered. Colliding labels
|
|
29
|
+
* are disambiguated with the hub, then the ordinal, so the result is injective.
|
|
30
|
+
*/
|
|
31
|
+
static label(input) {
|
|
32
|
+
const groups = CommunityLabeler.groupMembers(input.communityOf, input.nodes);
|
|
33
|
+
const internalDegree = CommunityLabeler.internalDegree(input.communityOf, input.edges);
|
|
34
|
+
const base = new Map();
|
|
35
|
+
const hubs = new Map();
|
|
36
|
+
for (const [index, members] of groups) {
|
|
37
|
+
const hub = CommunityLabeler.hubName(members, internalDegree);
|
|
38
|
+
hubs.set(index, hub);
|
|
39
|
+
base.set(index, CommunityLabeler.composeLabel(index, members, hub));
|
|
40
|
+
}
|
|
41
|
+
return CommunityLabeler.dedupe(base, hubs);
|
|
42
|
+
}
|
|
43
|
+
/** Buckets the labelled nodes by their community index, dropping nodes absent from the partition. */
|
|
44
|
+
static groupMembers(communityOf, nodes) {
|
|
45
|
+
const groups = new Map();
|
|
46
|
+
for (const node of nodes) {
|
|
47
|
+
const index = communityOf.get(node.id);
|
|
48
|
+
if (index === undefined) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const members = groups.get(index) ?? [];
|
|
52
|
+
members.push(node);
|
|
53
|
+
groups.set(index, members);
|
|
54
|
+
}
|
|
55
|
+
return groups;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Sums each node's weighted degree over edges that stay inside its community.
|
|
59
|
+
* Cross-community edges and self-loops are ignored, so the result ranks members
|
|
60
|
+
* by how strongly they couple to the rest of their own community.
|
|
61
|
+
*/
|
|
62
|
+
static internalDegree(communityOf, edges) {
|
|
63
|
+
const degree = new Map();
|
|
64
|
+
for (const edge of edges) {
|
|
65
|
+
if (edge.from === edge.to) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const from = communityOf.get(edge.from);
|
|
69
|
+
const to = communityOf.get(edge.to);
|
|
70
|
+
if (from === undefined || to === undefined || from !== to) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
degree.set(edge.from, (degree.get(edge.from) ?? 0) + edge.weight);
|
|
74
|
+
degree.set(edge.to, (degree.get(edge.to) ?? 0) + edge.weight);
|
|
75
|
+
}
|
|
76
|
+
return degree;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The name of the member with the greatest internal weighted degree, breaking
|
|
80
|
+
* ties by name then id so the choice is stable. Returns `undefined` only for an
|
|
81
|
+
* empty group.
|
|
82
|
+
*/
|
|
83
|
+
static hubName(members, internalDegree) {
|
|
84
|
+
if (members.length === 0) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
const ranked = [...members].sort((a, b) => {
|
|
88
|
+
const degreeA = internalDegree.get(a.id) ?? 0;
|
|
89
|
+
const degreeB = internalDegree.get(b.id) ?? 0;
|
|
90
|
+
if (degreeB !== degreeA) {
|
|
91
|
+
return degreeB - degreeA;
|
|
92
|
+
}
|
|
93
|
+
if (a.name !== b.name) {
|
|
94
|
+
return a.name < b.name ? -1 : 1;
|
|
95
|
+
}
|
|
96
|
+
return a.id < b.id ? -1 : 1;
|
|
97
|
+
});
|
|
98
|
+
return CommunityLabeler.displayName(ranked[0]);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* A node's human-facing name. `Module` nodes carry their file path as their
|
|
102
|
+
* name, which reads poorly as a label, so they display as the file's base name.
|
|
103
|
+
*/
|
|
104
|
+
static displayName(node) {
|
|
105
|
+
return node.kind === 'Module' ? CommunityLabeler.baseName(node.filePath) : node.name;
|
|
106
|
+
}
|
|
107
|
+
/** Builds the un-deduplicated label for one community from its area and hub. */
|
|
108
|
+
static composeLabel(index, members, hub) {
|
|
109
|
+
if (members.length === 0) {
|
|
110
|
+
return `community ${index}`;
|
|
111
|
+
}
|
|
112
|
+
const files = new Set(members.map((member) => member.filePath));
|
|
113
|
+
if (files.size === 1) {
|
|
114
|
+
const fileName = CommunityLabeler.baseName(members[0].filePath);
|
|
115
|
+
return fileName.length > 0 ? fileName : (hub ?? `community ${index}`);
|
|
116
|
+
}
|
|
117
|
+
const { directory, purity } = CommunityLabeler.dominantDirectory(members);
|
|
118
|
+
const area = CommunityLabeler.lastSegment(directory);
|
|
119
|
+
if (area.length > 0 && purity >= DIRECTORY_PURITY_THRESHOLD) {
|
|
120
|
+
return hub === undefined ? area : `${area} · ${hub}`;
|
|
121
|
+
}
|
|
122
|
+
if (hub !== undefined) {
|
|
123
|
+
return hub;
|
|
124
|
+
}
|
|
125
|
+
return area.length > 0 ? area : `community ${index}`;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The directory most members share, with the fraction of members under it. Ties
|
|
129
|
+
* favour the shorter (shallower) path, then lexical order, for determinism.
|
|
130
|
+
*/
|
|
131
|
+
static dominantDirectory(members) {
|
|
132
|
+
const counts = new Map();
|
|
133
|
+
for (const member of members) {
|
|
134
|
+
const directory = CommunityLabeler.directoryOf(member.filePath);
|
|
135
|
+
counts.set(directory, (counts.get(directory) ?? 0) + 1);
|
|
136
|
+
}
|
|
137
|
+
const ranked = [...counts.entries()].sort((a, b) => {
|
|
138
|
+
if (b[1] !== a[1]) {
|
|
139
|
+
return b[1] - a[1];
|
|
140
|
+
}
|
|
141
|
+
if (a[0].length !== b[0].length) {
|
|
142
|
+
return a[0].length - b[0].length;
|
|
143
|
+
}
|
|
144
|
+
return a[0] < b[0] ? -1 : 1;
|
|
145
|
+
});
|
|
146
|
+
const [directory, count] = ranked[0];
|
|
147
|
+
return { directory, purity: count / members.length };
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Resolves label collisions into a unique set: a duplicated label gains its
|
|
151
|
+
* community's hub (when that adds information), and any still-colliding label
|
|
152
|
+
* gains a `#index` suffix as a last resort.
|
|
153
|
+
*/
|
|
154
|
+
static dedupe(base, hubs) {
|
|
155
|
+
const withHub = CommunityLabeler.disambiguate(base, (index, label) => {
|
|
156
|
+
const hub = hubs.get(index);
|
|
157
|
+
return hub !== undefined && label.includes(hub) === false ? `${label} · ${hub}` : label;
|
|
158
|
+
});
|
|
159
|
+
return CommunityLabeler.disambiguate(withHub, (index, label) => `${label} #${index}`);
|
|
160
|
+
}
|
|
161
|
+
/** Applies `rename` to every label that more than one community currently shares. */
|
|
162
|
+
static disambiguate(labels, rename) {
|
|
163
|
+
const counts = new Map();
|
|
164
|
+
for (const label of labels.values()) {
|
|
165
|
+
counts.set(label, (counts.get(label) ?? 0) + 1);
|
|
166
|
+
}
|
|
167
|
+
const result = new Map();
|
|
168
|
+
for (const [index, label] of labels) {
|
|
169
|
+
result.set(index, (counts.get(label) ?? 0) > 1 ? rename(index, label) : label);
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
/** The directory portion of a POSIX-style path, or `''` for a bare file name. */
|
|
174
|
+
static directoryOf(filePath) {
|
|
175
|
+
const slash = filePath.lastIndexOf('/');
|
|
176
|
+
return slash === -1 ? '' : filePath.slice(0, slash);
|
|
177
|
+
}
|
|
178
|
+
/** The final segment of a directory path, or the whole path when it has no separator. */
|
|
179
|
+
static lastSegment(directory) {
|
|
180
|
+
if (directory.length === 0) {
|
|
181
|
+
return '';
|
|
182
|
+
}
|
|
183
|
+
const slash = directory.lastIndexOf('/');
|
|
184
|
+
return slash === -1 ? directory : directory.slice(slash + 1);
|
|
185
|
+
}
|
|
186
|
+
/** A file's base name without its directory or extension (`src/a/foo.ts` → `foo`). */
|
|
187
|
+
static baseName(filePath) {
|
|
188
|
+
const slash = filePath.lastIndexOf('/');
|
|
189
|
+
const fileName = slash === -1 ? filePath : filePath.slice(slash + 1);
|
|
190
|
+
const dot = fileName.lastIndexOf('.');
|
|
191
|
+
return dot <= 0 ? fileName : fileName.slice(0, dot);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=community_labeler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"community_labeler.js","sourceRoot":"","sources":["../../src/cluster/community_labeler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { EdgeKind } from '../schema/edge.js';
|
|
2
|
+
import { KuzuStore } from '../store/kuzu_store.js';
|
|
3
|
+
import { CommunityOptions } from './community_detector.js';
|
|
4
|
+
/** Namespaced key under which a node's community index is stored on its metadata. */
|
|
5
|
+
export declare const COMMUNITY_METADATA_KEY = "community";
|
|
6
|
+
/** Namespaced key under which a node's human-readable community label is stored on its metadata. */
|
|
7
|
+
export declare const COMMUNITY_LABEL_METADATA_KEY = "communityLabel";
|
|
8
|
+
/** Graph-level metadata key under which the clustering manifest is recorded. */
|
|
9
|
+
export declare const CLUSTERING_MANIFEST_KEY = "clustering";
|
|
10
|
+
/** The summary `cluster` returns and prints. */
|
|
11
|
+
export type ClusterReport = {
|
|
12
|
+
nodesAssigned: number;
|
|
13
|
+
communityCount: number;
|
|
14
|
+
quality: number;
|
|
15
|
+
resolution: number;
|
|
16
|
+
/** Member count per community, descending. */
|
|
17
|
+
sizes: number[];
|
|
18
|
+
/** Human-readable label per community, aligned with {@link ClusterReport.sizes}. */
|
|
19
|
+
labels: string[];
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Orchestrates a clustering pass over a loaded graph: read the weighted edges,
|
|
23
|
+
* detect communities with {@link CommunityDetector}, and attach the community
|
|
24
|
+
* index onto each node's metadata. Mirrors {@link RuntimeEnricher} — the pure
|
|
25
|
+
* algorithm lives in {@link CommunityDetector}; this class owns the store I/O.
|
|
26
|
+
*
|
|
27
|
+
* Existing node metadata is preserved; only the `community` key is written, so
|
|
28
|
+
* re-running is idempotent for an unchanged graph.
|
|
29
|
+
*/
|
|
30
|
+
export declare class GraphClusterer {
|
|
31
|
+
static cluster(store: KuzuStore, weights?: Partial<Record<EdgeKind, number>>, options?: CommunityOptions): Promise<ClusterReport>;
|
|
32
|
+
/** Flattens the per-index label map into an array aligned with community indices `0..count-1`. */
|
|
33
|
+
private static orderLabels;
|
|
34
|
+
/**
|
|
35
|
+
* Reads every edge whose kind carries a positive weight, resolving each to a
|
|
36
|
+
* {@link WeightedEdge} whose weight is the kind's coefficient times the edge's
|
|
37
|
+
* call-site `count`.
|
|
38
|
+
*/
|
|
39
|
+
private static readWeightedEdges;
|
|
40
|
+
/** Decodes an edge's call-site `count`, defaulting to 1 (the minimum the builder records). */
|
|
41
|
+
private static callCount;
|
|
42
|
+
/** Decodes a runtime call edge's `samples` weight, defaulting to 0. */
|
|
43
|
+
private static edgeSamples;
|
|
44
|
+
/** Decodes the JSON `metadata` column into a record; a missing, empty, or malformed value yields an empty record. */
|
|
45
|
+
private static parseMetadata;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=graph_clusterer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph_clusterer.d.ts","sourceRoot":"","sources":["../../src/cluster/graph_clusterer.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAqB,gBAAgB,EAA2C,MAAM,yBAAyB,CAAC;AAGvH,qFAAqF;AACrF,eAAO,MAAM,sBAAsB,cAAc,CAAC;AAElD,oGAAoG;AACpG,eAAO,MAAM,4BAA4B,mBAAmB,CAAC;AAE7D,gFAAgF;AAChF,eAAO,MAAM,uBAAuB,eAAe,CAAC;AAKpD,gDAAgD;AAChD,MAAM,MAAM,aAAa,GAAG;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,oFAAoF;IACpF,MAAM,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAEF;;;;;;;;GAQG;AACH,qBAAa,cAAc;WACb,OAAO,CACnB,KAAK,EAAE,SAAS,EAChB,OAAO,GAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAM,EAC/C,OAAO,GAAE,gBAA4C,GACnD,OAAO,CAAC,aAAa,CAAC;IAgDzB,kGAAkG;IAClG,OAAO,CAAC,MAAM,CAAC,WAAW;IAI1B;;;;OAIG;mBACkB,iBAAiB;IAwCtC,8FAA8F;IAC9F,OAAO,CAAC,MAAM,CAAC,SAAS;IAKxB,uEAAuE;IACvE,OAAO,CAAC,MAAM,CAAC,WAAW;IAK1B,qHAAqH;IACrH,OAAO,CAAC,MAAM,CAAC,aAAa;CAW5B"}
|