@monoes/monomindcli 1.6.3 → 1.6.5
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/.claude/commands/monomind-createtask.md +3 -3
- package/.claude/commands/monomind-do.md +2 -2
- package/.claude/commands/monomind-idea.md +2 -2
- package/.claude/helpers/graphify-freshen.cjs +15 -6
- package/README.md +307 -553
- package/bundled-graph/dist/src/analyze.d.ts +32 -0
- package/bundled-graph/dist/src/analyze.d.ts.map +1 -0
- package/bundled-graph/dist/src/analyze.js +297 -0
- package/bundled-graph/dist/src/analyze.js.map +1 -0
- package/bundled-graph/dist/src/build.d.ts +8 -0
- package/bundled-graph/dist/src/build.d.ts.map +1 -0
- package/bundled-graph/dist/src/build.js +73 -0
- package/bundled-graph/dist/src/build.js.map +1 -0
- package/bundled-graph/dist/src/cache.d.ts +12 -0
- package/bundled-graph/dist/src/cache.d.ts.map +1 -0
- package/bundled-graph/dist/src/cache.js +43 -0
- package/bundled-graph/dist/src/cache.js.map +1 -0
- package/bundled-graph/dist/src/cluster.d.ts +5 -0
- package/bundled-graph/dist/src/cluster.d.ts.map +1 -0
- package/bundled-graph/dist/src/cluster.js +120 -0
- package/bundled-graph/dist/src/cluster.js.map +1 -0
- package/bundled-graph/dist/src/detect.d.ts +21 -0
- package/bundled-graph/dist/src/detect.d.ts.map +1 -0
- package/bundled-graph/dist/src/detect.js +195 -0
- package/bundled-graph/dist/src/detect.js.map +1 -0
- package/bundled-graph/dist/src/export.d.ts +21 -0
- package/bundled-graph/dist/src/export.d.ts.map +1 -0
- package/bundled-graph/dist/src/export.js +68 -0
- package/bundled-graph/dist/src/export.js.map +1 -0
- package/bundled-graph/dist/src/extract/index.d.ts +20 -0
- package/bundled-graph/dist/src/extract/index.d.ts.map +1 -0
- package/bundled-graph/dist/src/extract/index.js +158 -0
- package/bundled-graph/dist/src/extract/index.js.map +1 -0
- package/bundled-graph/dist/src/extract/languages/c.d.ts +3 -0
- package/bundled-graph/dist/src/extract/languages/c.d.ts.map +1 -0
- package/bundled-graph/dist/src/extract/languages/c.js +88 -0
- package/bundled-graph/dist/src/extract/languages/c.js.map +1 -0
- package/bundled-graph/dist/src/extract/languages/cpp.d.ts +3 -0
- package/bundled-graph/dist/src/extract/languages/cpp.d.ts.map +1 -0
- package/bundled-graph/dist/src/extract/languages/cpp.js +121 -0
- package/bundled-graph/dist/src/extract/languages/cpp.js.map +1 -0
- package/bundled-graph/dist/src/extract/languages/csharp.d.ts +3 -0
- package/bundled-graph/dist/src/extract/languages/csharp.d.ts.map +1 -0
- package/bundled-graph/dist/src/extract/languages/csharp.js +121 -0
- package/bundled-graph/dist/src/extract/languages/csharp.js.map +1 -0
- package/bundled-graph/dist/src/extract/languages/go.d.ts +3 -0
- package/bundled-graph/dist/src/extract/languages/go.d.ts.map +1 -0
- package/bundled-graph/dist/src/extract/languages/go.js +181 -0
- package/bundled-graph/dist/src/extract/languages/go.js.map +1 -0
- package/bundled-graph/dist/src/extract/languages/java.d.ts +3 -0
- package/bundled-graph/dist/src/extract/languages/java.d.ts.map +1 -0
- package/bundled-graph/dist/src/extract/languages/java.js +117 -0
- package/bundled-graph/dist/src/extract/languages/java.js.map +1 -0
- package/bundled-graph/dist/src/extract/languages/kotlin.d.ts +3 -0
- package/bundled-graph/dist/src/extract/languages/kotlin.d.ts.map +1 -0
- package/bundled-graph/dist/src/extract/languages/kotlin.js +112 -0
- package/bundled-graph/dist/src/extract/languages/kotlin.js.map +1 -0
- package/bundled-graph/dist/src/extract/languages/php.d.ts +3 -0
- package/bundled-graph/dist/src/extract/languages/php.d.ts.map +1 -0
- package/bundled-graph/dist/src/extract/languages/php.js +130 -0
- package/bundled-graph/dist/src/extract/languages/php.js.map +1 -0
- package/bundled-graph/dist/src/extract/languages/python.d.ts +3 -0
- package/bundled-graph/dist/src/extract/languages/python.d.ts.map +1 -0
- package/bundled-graph/dist/src/extract/languages/python.js +230 -0
- package/bundled-graph/dist/src/extract/languages/python.js.map +1 -0
- package/bundled-graph/dist/src/extract/languages/ruby.d.ts +3 -0
- package/bundled-graph/dist/src/extract/languages/ruby.d.ts.map +1 -0
- package/bundled-graph/dist/src/extract/languages/ruby.js +120 -0
- package/bundled-graph/dist/src/extract/languages/ruby.js.map +1 -0
- package/bundled-graph/dist/src/extract/languages/rust.d.ts +3 -0
- package/bundled-graph/dist/src/extract/languages/rust.d.ts.map +1 -0
- package/bundled-graph/dist/src/extract/languages/rust.js +195 -0
- package/bundled-graph/dist/src/extract/languages/rust.js.map +1 -0
- package/bundled-graph/dist/src/extract/languages/scala.d.ts +3 -0
- package/bundled-graph/dist/src/extract/languages/scala.d.ts.map +1 -0
- package/bundled-graph/dist/src/extract/languages/scala.js +110 -0
- package/bundled-graph/dist/src/extract/languages/scala.js.map +1 -0
- package/bundled-graph/dist/src/extract/languages/swift.d.ts +3 -0
- package/bundled-graph/dist/src/extract/languages/swift.d.ts.map +1 -0
- package/bundled-graph/dist/src/extract/languages/swift.js +122 -0
- package/bundled-graph/dist/src/extract/languages/swift.js.map +1 -0
- package/bundled-graph/dist/src/extract/languages/typescript.d.ts +3 -0
- package/bundled-graph/dist/src/extract/languages/typescript.d.ts.map +1 -0
- package/bundled-graph/dist/src/extract/languages/typescript.js +295 -0
- package/bundled-graph/dist/src/extract/languages/typescript.js.map +1 -0
- package/bundled-graph/dist/src/extract/semantic.d.ts +38 -0
- package/bundled-graph/dist/src/extract/semantic.d.ts.map +1 -0
- package/bundled-graph/dist/src/extract/semantic.js +242 -0
- package/bundled-graph/dist/src/extract/semantic.js.map +1 -0
- package/bundled-graph/dist/src/extract/tree-sitter-runner.d.ts +48 -0
- package/bundled-graph/dist/src/extract/tree-sitter-runner.d.ts.map +1 -0
- package/bundled-graph/dist/src/extract/tree-sitter-runner.js +137 -0
- package/bundled-graph/dist/src/extract/tree-sitter-runner.js.map +1 -0
- package/bundled-graph/dist/src/extract/types.d.ts +7 -0
- package/bundled-graph/dist/src/extract/types.d.ts.map +1 -0
- package/bundled-graph/dist/src/extract/types.js +2 -0
- package/bundled-graph/dist/src/extract/types.js.map +1 -0
- package/bundled-graph/dist/src/index.d.ts +28 -0
- package/bundled-graph/dist/src/index.d.ts.map +1 -0
- package/bundled-graph/dist/src/index.js +26 -0
- package/bundled-graph/dist/src/index.js.map +1 -0
- package/bundled-graph/dist/src/pipeline.d.ts +27 -0
- package/bundled-graph/dist/src/pipeline.d.ts.map +1 -0
- package/bundled-graph/dist/src/pipeline.js +269 -0
- package/bundled-graph/dist/src/pipeline.js.map +1 -0
- package/bundled-graph/dist/src/report.d.ts +26 -0
- package/bundled-graph/dist/src/report.d.ts.map +1 -0
- package/bundled-graph/dist/src/report.js +214 -0
- package/bundled-graph/dist/src/report.js.map +1 -0
- package/bundled-graph/dist/src/types.d.ts +124 -0
- package/bundled-graph/dist/src/types.d.ts.map +1 -0
- package/bundled-graph/dist/src/types.js +2 -0
- package/bundled-graph/dist/src/types.js.map +1 -0
- package/bundled-graph/dist/src/visualize.d.ts +4 -0
- package/bundled-graph/dist/src/visualize.d.ts.map +1 -0
- package/bundled-graph/dist/src/visualize.js +574 -0
- package/bundled-graph/dist/src/visualize.js.map +1 -0
- package/bundled-graph/dist/tsconfig.tsbuildinfo +1 -0
- package/bundled-graph/package.json +57 -0
- package/dist/src/commands/memory.d.ts.map +1 -1
- package/dist/src/commands/memory.js +0 -1
- package/dist/src/commands/memory.js.map +1 -1
- package/dist/src/init/executor.d.ts.map +1 -1
- package/dist/src/init/executor.js +6 -1
- package/dist/src/init/executor.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -2
- /package/.claude/commands/{monobrain-help.md → monomind-help.md} +0 -0
- /package/.claude/commands/{monobrain-memory.md → monomind-memory.md} +0 -0
- /package/.claude/commands/{monobrain-swarm.md → monomind-swarm.md} +0 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type Graph from 'graphology';
|
|
2
|
+
import type { GodNode, SurpriseEdge, GraphAnalysis, GraphStats, GraphQuestion, GraphDiff, SerializedGraph } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Find the most connected nodes (god nodes) — core abstractions of the codebase.
|
|
5
|
+
* Sorted by total degree (in + out), descending.
|
|
6
|
+
*/
|
|
7
|
+
export declare function godNodes(graph: Graph, topN?: number): GodNode[];
|
|
8
|
+
/**
|
|
9
|
+
* Find surprising cross-community connections.
|
|
10
|
+
* An edge is surprising when its endpoints belong to different communities.
|
|
11
|
+
* Base score is degree(source) * degree(target), boosted by confidence and file type factors.
|
|
12
|
+
*/
|
|
13
|
+
export declare function surprisingConnections(graph: Graph, topN?: number): SurpriseEdge[];
|
|
14
|
+
/**
|
|
15
|
+
* Compute high-level graph statistics.
|
|
16
|
+
*/
|
|
17
|
+
export declare function graphStats(graph: Graph, graphPath?: string): GraphStats;
|
|
18
|
+
/**
|
|
19
|
+
* Build a complete GraphAnalysis object from an annotated graph.
|
|
20
|
+
* Assumes community detection has already been run (nodes have `community` attribute).
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildAnalysis(graph: Graph, graphPath?: string): GraphAnalysis;
|
|
23
|
+
/**
|
|
24
|
+
* Generate clarifying questions about uncertain or structurally interesting graph patterns.
|
|
25
|
+
* Sorted by priority (high → medium → low), capped at 25 total.
|
|
26
|
+
*/
|
|
27
|
+
export declare function suggestQuestions(graph: Graph, communities?: Record<number, string[]>): GraphQuestion[];
|
|
28
|
+
/**
|
|
29
|
+
* Compute the diff between two serialized graph snapshots.
|
|
30
|
+
*/
|
|
31
|
+
export declare function graphDiff(before: SerializedGraph, after: SerializedGraph): GraphDiff;
|
|
32
|
+
//# sourceMappingURL=analyze.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/analyze.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AACpC,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAc,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE1I;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,SAAK,GAAG,OAAO,EAAE,CAkB3D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,SAAK,GAAG,YAAY,EAAE,CAuC7E;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,UAAU,CAsCvE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,aAAa,CAgB7E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,aAAa,EAAE,CAgJtG;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,GAAG,SAAS,CAmCpF"}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find the most connected nodes (god nodes) — core abstractions of the codebase.
|
|
3
|
+
* Sorted by total degree (in + out), descending.
|
|
4
|
+
*/
|
|
5
|
+
export function godNodes(graph, topN = 15) {
|
|
6
|
+
const nodes = [];
|
|
7
|
+
graph.forEachNode((id, attrs) => {
|
|
8
|
+
nodes.push({
|
|
9
|
+
id,
|
|
10
|
+
label: attrs.label || id,
|
|
11
|
+
degree: graph.degree(id),
|
|
12
|
+
community: attrs.community,
|
|
13
|
+
sourceFile: attrs.sourceFile || '',
|
|
14
|
+
neighbors: graph
|
|
15
|
+
.neighbors(id)
|
|
16
|
+
.slice(0, 8)
|
|
17
|
+
.map((n) => graph.getNodeAttribute(n, 'label') || n),
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
return nodes.sort((a, b) => b.degree - a.degree).slice(0, topN);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Find surprising cross-community connections.
|
|
24
|
+
* An edge is surprising when its endpoints belong to different communities.
|
|
25
|
+
* Base score is degree(source) * degree(target), boosted by confidence and file type factors.
|
|
26
|
+
*/
|
|
27
|
+
export function surprisingConnections(graph, topN = 20) {
|
|
28
|
+
const surprises = [];
|
|
29
|
+
graph.forEachEdge((_, attrs, source, target) => {
|
|
30
|
+
const cu = graph.getNodeAttribute(source, 'community');
|
|
31
|
+
const cv = graph.getNodeAttribute(target, 'community');
|
|
32
|
+
if (cu !== undefined && cv !== undefined && cu !== cv) {
|
|
33
|
+
const confidence = attrs.confidence ?? 'AMBIGUOUS';
|
|
34
|
+
const confidenceScore = attrs.confidenceScore;
|
|
35
|
+
const sourceFileType = graph.getNodeAttribute(source, 'fileType');
|
|
36
|
+
const targetFileType = graph.getNodeAttribute(target, 'fileType');
|
|
37
|
+
let score = graph.degree(source) * graph.degree(target);
|
|
38
|
+
// Boost for AMBIGUOUS edges — more uncertain = more surprising
|
|
39
|
+
if (confidence === 'AMBIGUOUS')
|
|
40
|
+
score *= 1.5;
|
|
41
|
+
// Boost for cross-file-type connections
|
|
42
|
+
if (sourceFileType && targetFileType && sourceFileType !== targetFileType)
|
|
43
|
+
score *= 1.3;
|
|
44
|
+
// Boost for INFERRED edges based on confidence score
|
|
45
|
+
if (confidence === 'INFERRED')
|
|
46
|
+
score *= 1 + (confidenceScore ?? 0.5);
|
|
47
|
+
surprises.push({
|
|
48
|
+
from: graph.getNodeAttribute(source, 'label') || source,
|
|
49
|
+
fromCommunity: cu,
|
|
50
|
+
fromFile: graph.getNodeAttribute(source, 'sourceFile') || '',
|
|
51
|
+
to: graph.getNodeAttribute(target, 'label') || target,
|
|
52
|
+
toCommunity: cv,
|
|
53
|
+
toFile: graph.getNodeAttribute(target, 'sourceFile') || '',
|
|
54
|
+
relation: attrs.relation || '',
|
|
55
|
+
confidence,
|
|
56
|
+
score,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
return surprises.sort((a, b) => b.score - a.score).slice(0, topN);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Compute high-level graph statistics.
|
|
64
|
+
*/
|
|
65
|
+
export function graphStats(graph, graphPath) {
|
|
66
|
+
const confidence = {};
|
|
67
|
+
const relations = {};
|
|
68
|
+
const fileTypes = {};
|
|
69
|
+
const commSet = new Set();
|
|
70
|
+
graph.forEachEdge((_, attrs) => {
|
|
71
|
+
const c = attrs.confidence || 'UNKNOWN';
|
|
72
|
+
confidence[c] = (confidence[c] ?? 0) + 1;
|
|
73
|
+
const r = attrs.relation || 'unknown';
|
|
74
|
+
relations[r] = (relations[r] ?? 0) + 1;
|
|
75
|
+
});
|
|
76
|
+
graph.forEachNode((_, attrs) => {
|
|
77
|
+
const ft = attrs.fileType || 'unknown';
|
|
78
|
+
fileTypes[ft] = (fileTypes[ft] ?? 0) + 1;
|
|
79
|
+
const c = attrs.community;
|
|
80
|
+
if (c !== undefined)
|
|
81
|
+
commSet.add(c);
|
|
82
|
+
});
|
|
83
|
+
const topRelations = Object.fromEntries(Object.entries(relations)
|
|
84
|
+
.sort(([, a], [, b]) => b - a)
|
|
85
|
+
.slice(0, 10));
|
|
86
|
+
return {
|
|
87
|
+
nodes: graph.order,
|
|
88
|
+
edges: graph.size,
|
|
89
|
+
communities: commSet.size,
|
|
90
|
+
confidence: confidence,
|
|
91
|
+
fileTypes,
|
|
92
|
+
topRelations,
|
|
93
|
+
isDirected: graph.type === 'directed',
|
|
94
|
+
graphPath,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Build a complete GraphAnalysis object from an annotated graph.
|
|
99
|
+
* Assumes community detection has already been run (nodes have `community` attribute).
|
|
100
|
+
*/
|
|
101
|
+
export function buildAnalysis(graph, graphPath) {
|
|
102
|
+
const communities = {};
|
|
103
|
+
graph.forEachNode((id, attrs) => {
|
|
104
|
+
const c = attrs.community;
|
|
105
|
+
if (c !== undefined) {
|
|
106
|
+
if (!communities[c])
|
|
107
|
+
communities[c] = [];
|
|
108
|
+
communities[c].push(id);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
godNodes: godNodes(graph),
|
|
113
|
+
surprises: surprisingConnections(graph),
|
|
114
|
+
communities,
|
|
115
|
+
stats: graphStats(graph, graphPath),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Generate clarifying questions about uncertain or structurally interesting graph patterns.
|
|
120
|
+
* Sorted by priority (high → medium → low), capped at 25 total.
|
|
121
|
+
*/
|
|
122
|
+
export function suggestQuestions(graph, communities) {
|
|
123
|
+
const questions = [];
|
|
124
|
+
// Build communities map if not provided
|
|
125
|
+
const commMap = communities ?? {};
|
|
126
|
+
if (!communities) {
|
|
127
|
+
graph.forEachNode((id, attrs) => {
|
|
128
|
+
const c = attrs.community;
|
|
129
|
+
if (c !== undefined) {
|
|
130
|
+
if (!commMap[c])
|
|
131
|
+
commMap[c] = [];
|
|
132
|
+
commMap[c].push(id);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// AMBIGUOUS_EDGE: cross-community edges with AMBIGUOUS confidence
|
|
137
|
+
graph.forEachEdge((_, attrs, source, target) => {
|
|
138
|
+
if (attrs.confidence !== 'AMBIGUOUS')
|
|
139
|
+
return;
|
|
140
|
+
const cu = graph.getNodeAttribute(source, 'community');
|
|
141
|
+
const cv = graph.getNodeAttribute(target, 'community');
|
|
142
|
+
if (cu === undefined || cv === undefined || cu === cv)
|
|
143
|
+
return;
|
|
144
|
+
const from = graph.getNodeAttribute(source, 'label') || source;
|
|
145
|
+
const to = graph.getNodeAttribute(target, 'label') || target;
|
|
146
|
+
questions.push({
|
|
147
|
+
type: 'AMBIGUOUS_EDGE',
|
|
148
|
+
question: `What is the exact relationship between ${from} and ${to}? The edge is marked AMBIGUOUS.`,
|
|
149
|
+
nodes: [source, target],
|
|
150
|
+
priority: 'high',
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
// BRIDGE_NODE: nodes with neighbors in ≥3 different communities (simple heuristic)
|
|
154
|
+
const bridgeCandidates = [];
|
|
155
|
+
graph.forEachNode((id, attrs) => {
|
|
156
|
+
const neighborComms = new Set();
|
|
157
|
+
graph.neighbors(id).forEach((n) => {
|
|
158
|
+
const c = graph.getNodeAttribute(n, 'community');
|
|
159
|
+
if (c !== undefined)
|
|
160
|
+
neighborComms.add(c);
|
|
161
|
+
});
|
|
162
|
+
if (neighborComms.size >= 3) {
|
|
163
|
+
bridgeCandidates.push({
|
|
164
|
+
id,
|
|
165
|
+
label: attrs.label || id,
|
|
166
|
+
comms: Array.from(neighborComms),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
bridgeCandidates
|
|
171
|
+
.sort((a, b) => b.comms.length - a.comms.length)
|
|
172
|
+
.slice(0, 5)
|
|
173
|
+
.forEach(({ id, label, comms }) => {
|
|
174
|
+
questions.push({
|
|
175
|
+
type: 'BRIDGE_NODE',
|
|
176
|
+
question: `Is ${label} intentionally a bridge between communities [${comms.join(', ')}]? Should it be split?`,
|
|
177
|
+
nodes: [id],
|
|
178
|
+
priority: 'high',
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
// INFERRED_GOD_NODE: top 5 high-degree nodes where >50% of edges are INFERRED
|
|
182
|
+
const inferredGodNodes = [];
|
|
183
|
+
graph.forEachNode((id, attrs) => {
|
|
184
|
+
const edges = graph.edges(id);
|
|
185
|
+
if (edges.length === 0)
|
|
186
|
+
return;
|
|
187
|
+
const inferredCount = edges.filter((e) => graph.getEdgeAttribute(e, 'confidence') === 'INFERRED').length;
|
|
188
|
+
if (inferredCount / edges.length > 0.5) {
|
|
189
|
+
inferredGodNodes.push({
|
|
190
|
+
id,
|
|
191
|
+
label: attrs.label || id,
|
|
192
|
+
degree: graph.degree(id),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
inferredGodNodes
|
|
197
|
+
.sort((a, b) => b.degree - a.degree)
|
|
198
|
+
.slice(0, 5)
|
|
199
|
+
.forEach(({ id, label }) => {
|
|
200
|
+
questions.push({
|
|
201
|
+
type: 'INFERRED_GOD_NODE',
|
|
202
|
+
question: `Verify the connections of ${label}: most edges are inferred, not extracted.`,
|
|
203
|
+
nodes: [id],
|
|
204
|
+
priority: 'medium',
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
// LOW_COHESION_COMMUNITY: communities where >70% of edges are cross-community
|
|
208
|
+
const commIds = Object.keys(commMap).map(Number);
|
|
209
|
+
const lowCohesion = [];
|
|
210
|
+
for (const commId of commIds) {
|
|
211
|
+
const members = new Set(commMap[commId] ?? []);
|
|
212
|
+
if (members.size === 0)
|
|
213
|
+
continue;
|
|
214
|
+
let total = 0;
|
|
215
|
+
let crossEdges = 0;
|
|
216
|
+
members.forEach((nodeId) => {
|
|
217
|
+
graph.edges(nodeId).forEach((e) => {
|
|
218
|
+
total++;
|
|
219
|
+
const src = graph.source(e);
|
|
220
|
+
const tgt = graph.target(e);
|
|
221
|
+
const otherNode = src === nodeId ? tgt : src;
|
|
222
|
+
if (!members.has(otherNode))
|
|
223
|
+
crossEdges++;
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
// Each edge is counted twice (once per endpoint), halve the totals
|
|
227
|
+
total = Math.ceil(total / 2);
|
|
228
|
+
crossEdges = Math.ceil(crossEdges / 2);
|
|
229
|
+
if (total > 0 && crossEdges / total > 0.7) {
|
|
230
|
+
lowCohesion.push({ id: commId, ratio: crossEdges / total });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
lowCohesion
|
|
234
|
+
.sort((a, b) => b.ratio - a.ratio)
|
|
235
|
+
.slice(0, 3)
|
|
236
|
+
.forEach(({ id }) => {
|
|
237
|
+
questions.push({
|
|
238
|
+
type: 'LOW_COHESION_COMMUNITY',
|
|
239
|
+
question: `Community ${id} has low cohesion. Should it be split or reorganized?`,
|
|
240
|
+
nodes: commMap[id] ?? [],
|
|
241
|
+
priority: 'medium',
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
// ISOLATED_NODE: degree 0
|
|
245
|
+
let isolatedCount = 0;
|
|
246
|
+
graph.forEachNode((id, attrs) => {
|
|
247
|
+
if (isolatedCount >= 10)
|
|
248
|
+
return;
|
|
249
|
+
if (graph.degree(id) === 0) {
|
|
250
|
+
const label = attrs.label || id;
|
|
251
|
+
questions.push({
|
|
252
|
+
type: 'ISOLATED_NODE',
|
|
253
|
+
question: `Why is ${label} isolated (no connections)? It may need imports/references added.`,
|
|
254
|
+
nodes: [id],
|
|
255
|
+
priority: 'low',
|
|
256
|
+
});
|
|
257
|
+
isolatedCount++;
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
// Sort by priority: high → medium → low
|
|
261
|
+
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
|
262
|
+
questions.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
|
|
263
|
+
return questions.slice(0, 25);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Compute the diff between two serialized graph snapshots.
|
|
267
|
+
*/
|
|
268
|
+
export function graphDiff(before, after) {
|
|
269
|
+
const beforeNodeIds = new Set(before.nodes.map((n) => n.id));
|
|
270
|
+
const afterNodeIds = new Set(after.nodes.map((n) => n.id));
|
|
271
|
+
const addedNodes = after.nodes.filter((n) => !beforeNodeIds.has(n.id)).map((n) => n.id);
|
|
272
|
+
const removedNodes = before.nodes.filter((n) => !afterNodeIds.has(n.id)).map((n) => n.id);
|
|
273
|
+
const edgeKey = (e) => `${String(e.source)}|${String(e.target)}|${String(e['relation'] ?? '')}`;
|
|
274
|
+
const beforeEdgeKeys = new Set(before.links.map(edgeKey));
|
|
275
|
+
const afterEdgeKeys = new Set(after.links.map(edgeKey));
|
|
276
|
+
const addedEdges = after.links
|
|
277
|
+
.filter((e) => !beforeEdgeKeys.has(edgeKey(e)))
|
|
278
|
+
.map((e) => ({ source: String(e.source), target: String(e.target), relation: String(e['relation'] ?? '') }));
|
|
279
|
+
const removedEdges = before.links
|
|
280
|
+
.filter((e) => !afterEdgeKeys.has(edgeKey(e)))
|
|
281
|
+
.map((e) => ({ source: String(e.source), target: String(e.target), relation: String(e['relation'] ?? '') }));
|
|
282
|
+
// Community changes: nodes present in both snapshots with a changed community attribute
|
|
283
|
+
const beforeNodeMap = new Map(before.nodes.map((n) => [n.id, n]));
|
|
284
|
+
const communityChanges = [];
|
|
285
|
+
for (const afterNode of after.nodes) {
|
|
286
|
+
const beforeNode = beforeNodeMap.get(afterNode.id);
|
|
287
|
+
if (!beforeNode)
|
|
288
|
+
continue;
|
|
289
|
+
const oldCommunity = beforeNode['community'];
|
|
290
|
+
const newCommunity = afterNode['community'];
|
|
291
|
+
if (oldCommunity !== undefined && newCommunity !== undefined && oldCommunity !== newCommunity) {
|
|
292
|
+
communityChanges.push({ node: afterNode.id, oldCommunity, newCommunity });
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return { addedNodes, removedNodes, addedEdges, removedEdges, communityChanges };
|
|
296
|
+
}
|
|
297
|
+
//# sourceMappingURL=analyze.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../src/analyze.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import Graph from 'graphology';
|
|
2
|
+
import type { ExtractionResult } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Build a graphology Graph from extracted nodes and edges.
|
|
5
|
+
* Deduplicates nodes by id, merges parallel edges with higher weight.
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildGraph(extraction: ExtractionResult): Graph;
|
|
8
|
+
//# sourceMappingURL=build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,YAAY,CAAC;AAC/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,gBAAgB,GAAG,KAAK,CAsD9D"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import Graph from 'graphology';
|
|
2
|
+
// Mirrors upstream graphify's _normalize_id: lowercase + collapse non-alphanumeric to underscores.
|
|
3
|
+
function normalizeId(s) {
|
|
4
|
+
return s.replace(/[^a-zA-Z0-9]+/g, '_').replace(/^_+|_+$/g, '').toLowerCase();
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Build a graphology Graph from extracted nodes and edges.
|
|
8
|
+
* Deduplicates nodes by id, merges parallel edges with higher weight.
|
|
9
|
+
*/
|
|
10
|
+
export function buildGraph(extraction) {
|
|
11
|
+
const graph = new Graph({ type: 'directed', multi: false });
|
|
12
|
+
// Add all nodes — merge attributes if already present (dedup by id)
|
|
13
|
+
for (const node of extraction.nodes) {
|
|
14
|
+
if (!graph.hasNode(node.id)) {
|
|
15
|
+
graph.addNode(node.id, { ...node });
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
graph.mergeNodeAttributes(node.id, { ...node });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// Build normalized ID lookup to remap mismatched edge endpoints before stubbing.
|
|
22
|
+
const normToId = new Map();
|
|
23
|
+
graph.forEachNode((id) => {
|
|
24
|
+
normToId.set(normalizeId(id), id);
|
|
25
|
+
});
|
|
26
|
+
// Add edges — skip self-loops, remap via normalization, stub only true externals
|
|
27
|
+
for (const edge of extraction.edges) {
|
|
28
|
+
let src = edge.source;
|
|
29
|
+
let tgt = edge.target;
|
|
30
|
+
if (!graph.hasNode(src)) {
|
|
31
|
+
const remapped = normToId.get(normalizeId(src));
|
|
32
|
+
if (remapped) {
|
|
33
|
+
src = remapped;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
graph.addNode(src, { id: src, label: src, fileType: 'unknown', sourceFile: '' });
|
|
37
|
+
normToId.set(normalizeId(src), src);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (!graph.hasNode(tgt)) {
|
|
41
|
+
const remapped = normToId.get(normalizeId(tgt));
|
|
42
|
+
if (remapped) {
|
|
43
|
+
tgt = remapped;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
graph.addNode(tgt, { id: tgt, label: tgt, fileType: 'unknown', sourceFile: '' });
|
|
47
|
+
normToId.set(normalizeId(tgt), tgt);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (src === tgt)
|
|
51
|
+
continue;
|
|
52
|
+
try {
|
|
53
|
+
graph.addEdge(src, tgt, {
|
|
54
|
+
relation: edge.relation,
|
|
55
|
+
confidence: edge.confidence,
|
|
56
|
+
confidenceScore: edge.confidenceScore,
|
|
57
|
+
weight: edge.weight ?? 1,
|
|
58
|
+
sourceFile: edge.sourceFile,
|
|
59
|
+
sourceLocation: edge.sourceLocation,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Edge already exists — bump its weight
|
|
64
|
+
const existing = graph.edge(src, tgt);
|
|
65
|
+
if (existing) {
|
|
66
|
+
const prev = graph.getEdgeAttribute(existing, 'weight') ?? 1;
|
|
67
|
+
graph.setEdgeAttribute(existing, 'weight', prev + 1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return graph;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,YAAY,CAAC;AAG/B;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,UAA4B;IACrD,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAE5D,oEAAoE;IACpE,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;QACpC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YAC5B,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;QACtC,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,mBAAmB,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED,2DAA2D;IAC3D,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;YAAE,SAAS;QAE1C,mEAAmE;QACnE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAChC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE;gBACzB,EAAE,EAAE,IAAI,CAAC,MAAM;gBACf,KAAK,EAAE,IAAI,CAAC,MAAM;gBAClB,QAAQ,EAAE,SAAS;gBACnB,UAAU,EAAE,EAAE;aACf,CAAC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAChC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE;gBACzB,EAAE,EAAE,IAAI,CAAC,MAAM;gBACf,KAAK,EAAE,IAAI,CAAC,MAAM;gBAClB,QAAQ,EAAE,SAAS;gBACnB,UAAU,EAAE,EAAE;aACf,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC;YACH,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE;gBACtC,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,eAAe,EAAE,IAAI,CAAC,eAAe;gBACrC,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC;gBACxB,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,cAAc,EAAE,IAAI,CAAC,cAAc;aACpC,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,wCAAwC;YACxC,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YACtD,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,IAAI,GAAI,KAAK,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAY,IAAI,CAAC,CAAC;gBACzE,KAAK,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC;YACvD,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ExtractionResult } from './types.js';
|
|
2
|
+
export declare class FileCache {
|
|
3
|
+
private cacheDir;
|
|
4
|
+
constructor(outputDir: string);
|
|
5
|
+
/** Strip YAML frontmatter (--- blocks) before hashing so metadata-only changes don't bust the cache. */
|
|
6
|
+
private stripFrontmatter;
|
|
7
|
+
key(filePath: string, content: string): string;
|
|
8
|
+
get(key: string): ExtractionResult | null;
|
|
9
|
+
set(key: string, result: ExtractionResult): void;
|
|
10
|
+
has(key: string): boolean;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAS;gBAEb,SAAS,EAAE,MAAM;IAK7B,wGAAwG;IACxG,OAAO,CAAC,gBAAgB;IAMxB,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;IAM9C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI;IAUzC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAOhD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;CAG1B"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
export class FileCache {
|
|
5
|
+
cacheDir;
|
|
6
|
+
constructor(outputDir) {
|
|
7
|
+
this.cacheDir = join(outputDir, 'cache');
|
|
8
|
+
mkdirSync(this.cacheDir, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
/** Strip YAML frontmatter (--- blocks) before hashing so metadata-only changes don't bust the cache. */
|
|
11
|
+
stripFrontmatter(content) {
|
|
12
|
+
if (!content.startsWith('---'))
|
|
13
|
+
return content;
|
|
14
|
+
const end = content.indexOf('\n---', 3);
|
|
15
|
+
return end === -1 ? content : content.slice(end + 4);
|
|
16
|
+
}
|
|
17
|
+
key(filePath, content) {
|
|
18
|
+
return createHash('sha256')
|
|
19
|
+
.update(filePath + this.stripFrontmatter(content))
|
|
20
|
+
.digest('hex');
|
|
21
|
+
}
|
|
22
|
+
get(key) {
|
|
23
|
+
const p = join(this.cacheDir, `${key}.json`);
|
|
24
|
+
if (!existsSync(p))
|
|
25
|
+
return null;
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(readFileSync(p, 'utf-8'));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
set(key, result) {
|
|
34
|
+
const p = join(this.cacheDir, `${key}.json`);
|
|
35
|
+
const tmp = `${p}.tmp`;
|
|
36
|
+
writeFileSync(tmp, JSON.stringify(result), 'utf-8');
|
|
37
|
+
renameSync(tmp, p);
|
|
38
|
+
}
|
|
39
|
+
has(key) {
|
|
40
|
+
return existsSync(join(this.cacheDir, `${key}.json`));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AACpF,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,MAAM,OAAO,SAAS;IACZ,QAAQ,CAAS;IAEzB,YAAY,SAAiB;QAC3B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACzC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,wGAAwG;IAChG,gBAAgB,CAAC,OAAe;QACtC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC;YAAE,OAAO,OAAO,CAAC;QAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACxC,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IACvD,CAAC;IAED,GAAG,CAAC,QAAgB,EAAE,OAAe;QACnC,OAAO,UAAU,CAAC,QAAQ,CAAC;aACxB,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;aACjD,MAAM,CAAC,KAAK,CAAC,CAAC;IACnB,CAAC;IAED,GAAG,CAAC,GAAW;QACb,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC;QAC7C,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QAChC,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAAqB,CAAC;QAClE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,MAAwB;QACvC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC;QAC7C,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC;QACvB,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;QACpD,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACrB,CAAC;IAED,GAAG,CAAC,GAAW;QACb,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC;IACxD,CAAC;CACF"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type Graph from 'graphology';
|
|
2
|
+
export declare function detectCommunities(graph: Graph): Promise<Record<number, string[]>>;
|
|
3
|
+
export declare function cohesionScore(graph: Graph, communityNodes: string[]): number;
|
|
4
|
+
export declare function splitOversizedCommunities(graph: Graph, communities: Record<number, string[]>, threshold?: number, louvainFn?: ((g: Graph) => Record<string, number>) | null): Record<number, string[]>;
|
|
5
|
+
//# sourceMappingURL=cluster.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cluster.d.ts","sourceRoot":"","sources":["../../src/cluster.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AAEpC,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAkBvF;AAsBD,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,MAAM,CAe5E;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EACrC,SAAS,SAAO,GACf,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CA6B1B"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import Graph from 'graphology';
|
|
2
|
+
export async function detectCommunities(graph) {
|
|
3
|
+
let louvainFn = null;
|
|
4
|
+
try {
|
|
5
|
+
const mod = await import('graphology-communities-louvain');
|
|
6
|
+
louvainFn = mod.default;
|
|
7
|
+
}
|
|
8
|
+
catch { /* louvain not available */ }
|
|
9
|
+
if (louvainFn) {
|
|
10
|
+
try {
|
|
11
|
+
const assignment = louvainFn(graph);
|
|
12
|
+
for (const [nodeId, communityId] of Object.entries(assignment)) {
|
|
13
|
+
graph.setNodeAttribute(nodeId, 'community', communityId);
|
|
14
|
+
}
|
|
15
|
+
const communities = {};
|
|
16
|
+
for (const [nodeId, communityId] of Object.entries(assignment)) {
|
|
17
|
+
if (!communities[communityId])
|
|
18
|
+
communities[communityId] = [];
|
|
19
|
+
communities[communityId].push(nodeId);
|
|
20
|
+
}
|
|
21
|
+
return splitOversizedCommunities(graph, communities, 0.25, louvainFn);
|
|
22
|
+
}
|
|
23
|
+
catch { /* fall through */ }
|
|
24
|
+
}
|
|
25
|
+
return splitOversizedCommunities(graph, fallbackCluster(graph), 0.25, louvainFn);
|
|
26
|
+
}
|
|
27
|
+
function fallbackCluster(graph) {
|
|
28
|
+
const dirMap = new Map();
|
|
29
|
+
let nextId = 0;
|
|
30
|
+
const communities = {};
|
|
31
|
+
graph.forEachNode((id, attrs) => {
|
|
32
|
+
const file = attrs.sourceFile || '';
|
|
33
|
+
const parts = file.split('/');
|
|
34
|
+
const dir = parts.length > 1 ? parts.slice(0, -1).join('/') : 'root';
|
|
35
|
+
if (!dirMap.has(dir))
|
|
36
|
+
dirMap.set(dir, nextId++);
|
|
37
|
+
const cid = dirMap.get(dir);
|
|
38
|
+
graph.setNodeAttribute(id, 'community', cid);
|
|
39
|
+
if (!communities[cid])
|
|
40
|
+
communities[cid] = [];
|
|
41
|
+
communities[cid].push(id);
|
|
42
|
+
});
|
|
43
|
+
return communities;
|
|
44
|
+
}
|
|
45
|
+
export function cohesionScore(graph, communityNodes) {
|
|
46
|
+
const memberSet = new Set(communityNodes);
|
|
47
|
+
let totalEdges = 0;
|
|
48
|
+
let internalEdges = 0;
|
|
49
|
+
graph.forEachEdge((_edge, _attrs, source, target) => {
|
|
50
|
+
const srcIn = memberSet.has(source);
|
|
51
|
+
const tgtIn = memberSet.has(target);
|
|
52
|
+
if (srcIn || tgtIn) {
|
|
53
|
+
totalEdges++;
|
|
54
|
+
if (srcIn && tgtIn)
|
|
55
|
+
internalEdges++;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
return totalEdges === 0 ? 1.0 : internalEdges / totalEdges;
|
|
59
|
+
}
|
|
60
|
+
export function splitOversizedCommunities(graph, communities, threshold = 0.25, louvainFn = null) {
|
|
61
|
+
const maxSize = threshold * graph.order;
|
|
62
|
+
const allIds = Object.keys(communities).map(Number);
|
|
63
|
+
let nextId = allIds.length > 0 ? Math.max(...allIds) + 1 : 0;
|
|
64
|
+
for (const [cidStr, members] of Object.entries(communities)) {
|
|
65
|
+
if (members.length <= maxSize)
|
|
66
|
+
continue;
|
|
67
|
+
const cid = Number(cidStr);
|
|
68
|
+
// Attempt topology-based second pass via Louvain on the community subgraph
|
|
69
|
+
if (louvainFn && members.length >= 10) {
|
|
70
|
+
try {
|
|
71
|
+
const memberSet = new Set(members);
|
|
72
|
+
const subG = new Graph({ type: 'undirected', multi: false });
|
|
73
|
+
for (const nodeId of members)
|
|
74
|
+
subG.addNode(nodeId);
|
|
75
|
+
graph.forEachEdge((_e, _a, source, target) => {
|
|
76
|
+
if (memberSet.has(source) && memberSet.has(target) && source !== target && !subG.hasEdge(source, target))
|
|
77
|
+
subG.addEdge(source, target);
|
|
78
|
+
});
|
|
79
|
+
const subAssignment = louvainFn(subG);
|
|
80
|
+
const subCommunityCount = new Set(Object.values(subAssignment)).size;
|
|
81
|
+
if (subCommunityCount > 1 && subCommunityCount <= Math.ceil(members.length / 2)) {
|
|
82
|
+
const subIdMap = new Map();
|
|
83
|
+
const newSubIds = {};
|
|
84
|
+
for (const [nodeId, localId] of Object.entries(subAssignment)) {
|
|
85
|
+
if (!subIdMap.has(localId))
|
|
86
|
+
subIdMap.set(localId, nextId++);
|
|
87
|
+
const globalId = subIdMap.get(localId);
|
|
88
|
+
graph.setNodeAttribute(nodeId, 'community', globalId);
|
|
89
|
+
if (!newSubIds[globalId])
|
|
90
|
+
newSubIds[globalId] = [];
|
|
91
|
+
newSubIds[globalId].push(nodeId);
|
|
92
|
+
}
|
|
93
|
+
delete communities[cid];
|
|
94
|
+
Object.assign(communities, newSubIds);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch { /* fall through to directory heuristic */ }
|
|
99
|
+
}
|
|
100
|
+
// Directory heuristic fallback
|
|
101
|
+
const subMap = new Map();
|
|
102
|
+
const newSubIds = {};
|
|
103
|
+
for (const nodeId of members) {
|
|
104
|
+
const file = graph.getNodeAttribute(nodeId, 'sourceFile') || '';
|
|
105
|
+
const parts = file.split('/');
|
|
106
|
+
const parentDir = parts.length > 1 ? parts[parts.length - 2] : 'root';
|
|
107
|
+
if (!subMap.has(parentDir))
|
|
108
|
+
subMap.set(parentDir, nextId++);
|
|
109
|
+
const subId = subMap.get(parentDir);
|
|
110
|
+
graph.setNodeAttribute(nodeId, 'community', subId);
|
|
111
|
+
if (!newSubIds[subId])
|
|
112
|
+
newSubIds[subId] = [];
|
|
113
|
+
newSubIds[subId].push(nodeId);
|
|
114
|
+
}
|
|
115
|
+
delete communities[cid];
|
|
116
|
+
Object.assign(communities, newSubIds);
|
|
117
|
+
}
|
|
118
|
+
return communities;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=cluster.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cluster.js","sourceRoot":"","sources":["../../src/cluster.ts"],"names":[],"mappings":"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"}
|