sverklo 0.5.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/sverklo.js +194 -9
- package/dist/bin/sverklo.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/registry/indexer-pool.d.ts +24 -0
- package/dist/src/registry/indexer-pool.js +97 -0
- package/dist/src/registry/indexer-pool.js.map +1 -0
- package/dist/src/registry/registry.d.ts +18 -0
- package/dist/src/registry/registry.js +69 -0
- package/dist/src/registry/registry.js.map +1 -0
- package/dist/src/registry/registry.test.d.ts +1 -0
- package/dist/src/registry/registry.test.js +79 -0
- package/dist/src/registry/registry.test.js.map +1 -0
- package/dist/src/search/cluster.d.ts +25 -0
- package/dist/src/search/cluster.js +216 -0
- package/dist/src/search/cluster.js.map +1 -0
- package/dist/src/server/dashboard-html.js +324 -183
- package/dist/src/server/dashboard-html.js.map +1 -1
- package/dist/src/server/http-server.js +29 -0
- package/dist/src/server/http-server.js.map +1 -1
- package/dist/src/server/mcp-server.d.ts +1 -0
- package/dist/src/server/mcp-server.js +319 -0
- package/dist/src/server/mcp-server.js.map +1 -1
- package/dist/src/server/tools/clusters.d.ts +20 -0
- package/dist/src/server/tools/clusters.js +92 -0
- package/dist/src/server/tools/clusters.js.map +1 -0
- package/dist/src/server/tools/list-repos.d.ts +9 -0
- package/dist/src/server/tools/list-repos.js +48 -0
- package/dist/src/server/tools/list-repos.js.map +1 -0
- package/dist/src/wiki/wiki-generator.d.ts +12 -0
- package/dist/src/wiki/wiki-generator.js +357 -0
- package/dist/src/wiki/wiki-generator.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { mkdirSync, writeFileSync, rmSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
// We need to mock homedir() to isolate from the real ~/.sverklo
|
|
6
|
+
const testDir = join(tmpdir(), `sverklo-registry-test-${process.pid}`);
|
|
7
|
+
vi.mock("node:os", async () => {
|
|
8
|
+
const actual = await vi.importActual("node:os");
|
|
9
|
+
return {
|
|
10
|
+
...actual,
|
|
11
|
+
homedir: () => testDir,
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
// Import after mock is set up
|
|
15
|
+
const { getRegistry, registerRepo, unregisterRepo, getRegistryPath, deriveRepoName, updateLastIndexed, } = await import("./registry.js");
|
|
16
|
+
describe("registry", () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
mkdirSync(join(testDir, ".sverklo"), { recursive: true });
|
|
19
|
+
});
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
22
|
+
});
|
|
23
|
+
it("returns empty object when no registry file exists", () => {
|
|
24
|
+
expect(getRegistry()).toEqual({});
|
|
25
|
+
});
|
|
26
|
+
it("registers and retrieves a repo", () => {
|
|
27
|
+
registerRepo("my-app", "/dev/my-app");
|
|
28
|
+
const repos = getRegistry();
|
|
29
|
+
expect(repos["my-app"]).toBeDefined();
|
|
30
|
+
expect(repos["my-app"].path).toBe("/dev/my-app");
|
|
31
|
+
expect(repos["my-app"].name).toBe("my-app");
|
|
32
|
+
expect(repos["my-app"].lastIndexed).toBeDefined();
|
|
33
|
+
});
|
|
34
|
+
it("unregisters a repo", () => {
|
|
35
|
+
registerRepo("my-app", "/dev/my-app");
|
|
36
|
+
registerRepo("other", "/dev/other");
|
|
37
|
+
unregisterRepo("my-app");
|
|
38
|
+
const repos = getRegistry();
|
|
39
|
+
expect(repos["my-app"]).toBeUndefined();
|
|
40
|
+
expect(repos["other"]).toBeDefined();
|
|
41
|
+
});
|
|
42
|
+
it("updates lastIndexed timestamp", () => {
|
|
43
|
+
registerRepo("my-app", "/dev/my-app");
|
|
44
|
+
const before = getRegistry()["my-app"].lastIndexed;
|
|
45
|
+
// Small delay to ensure timestamp differs
|
|
46
|
+
updateLastIndexed("my-app");
|
|
47
|
+
const after = getRegistry()["my-app"].lastIndexed;
|
|
48
|
+
expect(after).toBeDefined();
|
|
49
|
+
// They may or may not differ depending on timing, but both should be valid ISO dates
|
|
50
|
+
expect(new Date(after).getTime()).toBeGreaterThanOrEqual(new Date(before).getTime());
|
|
51
|
+
});
|
|
52
|
+
it("getRegistryPath returns a path under ~/.sverklo", () => {
|
|
53
|
+
const p = getRegistryPath();
|
|
54
|
+
expect(p).toContain(".sverklo");
|
|
55
|
+
expect(p).toContain("registry.json");
|
|
56
|
+
});
|
|
57
|
+
it("deriveRepoName uses basename by default", () => {
|
|
58
|
+
expect(deriveRepoName("/dev/my-cool-project")).toBe("my-cool-project");
|
|
59
|
+
});
|
|
60
|
+
it("deriveRepoName deduplicates on collision", () => {
|
|
61
|
+
registerRepo("shared", "/dev/team-a/shared");
|
|
62
|
+
// Different path, same basename
|
|
63
|
+
const name = deriveRepoName("/dev/team-b/shared");
|
|
64
|
+
expect(name).not.toBe("shared");
|
|
65
|
+
expect(name).toContain("shared");
|
|
66
|
+
});
|
|
67
|
+
it("registry file is valid JSON", () => {
|
|
68
|
+
registerRepo("foo", "/dev/foo");
|
|
69
|
+
const raw = readFileSync(getRegistryPath(), "utf-8");
|
|
70
|
+
const parsed = JSON.parse(raw);
|
|
71
|
+
expect(parsed.repos).toBeDefined();
|
|
72
|
+
expect(parsed.repos.foo.path).toBe("/dev/foo");
|
|
73
|
+
});
|
|
74
|
+
it("handles corrupt registry file gracefully", () => {
|
|
75
|
+
writeFileSync(getRegistryPath(), "NOT JSON{{{");
|
|
76
|
+
expect(getRegistry()).toEqual({});
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
//# sourceMappingURL=registry.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.test.js","sourceRoot":"","sources":["../../../src/registry/registry.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface FileCluster {
|
|
2
|
+
id: number;
|
|
3
|
+
name: string;
|
|
4
|
+
files: Array<{
|
|
5
|
+
path: string;
|
|
6
|
+
pagerank: number;
|
|
7
|
+
language: string;
|
|
8
|
+
}>;
|
|
9
|
+
hubFile: string;
|
|
10
|
+
size: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Detect functional clusters in the dependency graph using Label Propagation.
|
|
14
|
+
* Groups tightly-connected files into clusters based on import relationships.
|
|
15
|
+
*/
|
|
16
|
+
export declare function detectClusters(files: Array<{
|
|
17
|
+
id: number;
|
|
18
|
+
path: string;
|
|
19
|
+
pagerank: number;
|
|
20
|
+
language: string;
|
|
21
|
+
}>, edges: Array<{
|
|
22
|
+
source: number;
|
|
23
|
+
target: number;
|
|
24
|
+
weight: number;
|
|
25
|
+
}>): FileCluster[];
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// Functional cluster detection using Label Propagation Algorithm (LPA).
|
|
2
|
+
// Groups tightly-connected files into clusters based on import relationships.
|
|
3
|
+
// No external dependencies — pure TypeScript, O(E * iterations).
|
|
4
|
+
const MAX_ITERATIONS = 20;
|
|
5
|
+
const MIN_CLUSTER_SIZE = 3;
|
|
6
|
+
/**
|
|
7
|
+
* Detect functional clusters in the dependency graph using Label Propagation.
|
|
8
|
+
* Groups tightly-connected files into clusters based on import relationships.
|
|
9
|
+
*/
|
|
10
|
+
export function detectClusters(files, edges) {
|
|
11
|
+
if (files.length === 0)
|
|
12
|
+
return [];
|
|
13
|
+
// Build adjacency list (undirected — imports go both ways for clustering)
|
|
14
|
+
const neighbors = new Map();
|
|
15
|
+
for (const f of files) {
|
|
16
|
+
neighbors.set(f.id, []);
|
|
17
|
+
}
|
|
18
|
+
for (const e of edges) {
|
|
19
|
+
neighbors.get(e.source)?.push({ neighbor: e.target, weight: e.weight });
|
|
20
|
+
neighbors.get(e.target)?.push({ neighbor: e.source, weight: e.weight });
|
|
21
|
+
}
|
|
22
|
+
// Initialize: each node gets its own label
|
|
23
|
+
const nodes = new Map();
|
|
24
|
+
for (const f of files) {
|
|
25
|
+
nodes.set(f.id, {
|
|
26
|
+
id: f.id,
|
|
27
|
+
path: f.path,
|
|
28
|
+
pagerank: f.pagerank,
|
|
29
|
+
language: f.language,
|
|
30
|
+
label: f.id,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// Iterate label propagation
|
|
34
|
+
const nodeIds = files.map(f => f.id);
|
|
35
|
+
for (let iter = 0; iter < MAX_ITERATIONS; iter++) {
|
|
36
|
+
let changed = false;
|
|
37
|
+
// Shuffle node order each iteration to break ties fairly
|
|
38
|
+
shuffleInPlace(nodeIds);
|
|
39
|
+
for (const nodeId of nodeIds) {
|
|
40
|
+
const node = nodes.get(nodeId);
|
|
41
|
+
const nbrs = neighbors.get(nodeId);
|
|
42
|
+
if (!nbrs || nbrs.length === 0)
|
|
43
|
+
continue;
|
|
44
|
+
// Count weighted votes for each label among neighbors
|
|
45
|
+
const labelWeights = new Map();
|
|
46
|
+
for (const { neighbor, weight } of nbrs) {
|
|
47
|
+
const nbrNode = nodes.get(neighbor);
|
|
48
|
+
if (!nbrNode)
|
|
49
|
+
continue;
|
|
50
|
+
const label = nbrNode.label;
|
|
51
|
+
labelWeights.set(label, (labelWeights.get(label) || 0) + weight);
|
|
52
|
+
}
|
|
53
|
+
// Pick the label with the highest total weight
|
|
54
|
+
let bestLabel = node.label;
|
|
55
|
+
let bestWeight = -1;
|
|
56
|
+
for (const [label, w] of labelWeights) {
|
|
57
|
+
if (w > bestWeight) {
|
|
58
|
+
bestWeight = w;
|
|
59
|
+
bestLabel = label;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (bestLabel !== node.label) {
|
|
63
|
+
node.label = bestLabel;
|
|
64
|
+
changed = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (!changed)
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
// Group nodes by label
|
|
71
|
+
const labelGroups = new Map();
|
|
72
|
+
for (const node of nodes.values()) {
|
|
73
|
+
let group = labelGroups.get(node.label);
|
|
74
|
+
if (!group) {
|
|
75
|
+
group = [];
|
|
76
|
+
labelGroups.set(node.label, group);
|
|
77
|
+
}
|
|
78
|
+
group.push(node);
|
|
79
|
+
}
|
|
80
|
+
// Post-process: merge tiny clusters into nearest larger neighbor
|
|
81
|
+
const largeClusters = new Map();
|
|
82
|
+
const tinyClusters = [];
|
|
83
|
+
for (const [label, group] of labelGroups) {
|
|
84
|
+
if (group.length >= MIN_CLUSTER_SIZE) {
|
|
85
|
+
largeClusters.set(label, group);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
tinyClusters.push(group);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// For each tiny cluster, find the large cluster it's most connected to
|
|
92
|
+
for (const tinyGroup of tinyClusters) {
|
|
93
|
+
const connectionWeights = new Map();
|
|
94
|
+
for (const node of tinyGroup) {
|
|
95
|
+
const nbrs = neighbors.get(node.id);
|
|
96
|
+
if (!nbrs)
|
|
97
|
+
continue;
|
|
98
|
+
for (const { neighbor, weight } of nbrs) {
|
|
99
|
+
const nbrNode = nodes.get(neighbor);
|
|
100
|
+
if (!nbrNode)
|
|
101
|
+
continue;
|
|
102
|
+
// Only consider merging into large clusters
|
|
103
|
+
if (largeClusters.has(nbrNode.label)) {
|
|
104
|
+
connectionWeights.set(nbrNode.label, (connectionWeights.get(nbrNode.label) || 0) + weight);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Find best large cluster to merge into
|
|
109
|
+
let bestLabel = -1;
|
|
110
|
+
let bestWeight = 0;
|
|
111
|
+
for (const [label, w] of connectionWeights) {
|
|
112
|
+
if (w > bestWeight) {
|
|
113
|
+
bestWeight = w;
|
|
114
|
+
bestLabel = label;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (bestLabel !== -1) {
|
|
118
|
+
// Merge into the large cluster
|
|
119
|
+
const target = largeClusters.get(bestLabel);
|
|
120
|
+
for (const node of tinyGroup) {
|
|
121
|
+
node.label = bestLabel;
|
|
122
|
+
target.push(node);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// No large neighbor — keep as its own cluster if it has edges,
|
|
127
|
+
// otherwise discard isolated singletons
|
|
128
|
+
if (tinyGroup.length > 1 || tinyGroup.some(n => (neighbors.get(n.id)?.length ?? 0) > 0)) {
|
|
129
|
+
const label = tinyGroup[0].label;
|
|
130
|
+
largeClusters.set(label, tinyGroup);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Build final FileCluster array
|
|
135
|
+
const clusters = [];
|
|
136
|
+
let clusterId = 1;
|
|
137
|
+
// Sort clusters by total PageRank (most important first)
|
|
138
|
+
const sortedGroups = [...largeClusters.values()].sort((a, b) => {
|
|
139
|
+
const sumA = a.reduce((s, n) => s + n.pagerank, 0);
|
|
140
|
+
const sumB = b.reduce((s, n) => s + n.pagerank, 0);
|
|
141
|
+
return sumB - sumA;
|
|
142
|
+
});
|
|
143
|
+
for (const group of sortedGroups) {
|
|
144
|
+
// Sort files within cluster by PageRank descending
|
|
145
|
+
group.sort((a, b) => b.pagerank - a.pagerank);
|
|
146
|
+
const hubFile = group[0].path;
|
|
147
|
+
const name = computeClusterName(group.map(n => n.path));
|
|
148
|
+
clusters.push({
|
|
149
|
+
id: clusterId++,
|
|
150
|
+
name,
|
|
151
|
+
files: group.map(n => ({
|
|
152
|
+
path: n.path,
|
|
153
|
+
pagerank: n.pagerank,
|
|
154
|
+
language: n.language,
|
|
155
|
+
})),
|
|
156
|
+
hubFile,
|
|
157
|
+
size: group.length,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return clusters;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Compute a human-readable cluster name from the common directory prefix
|
|
164
|
+
* of its files. Falls back to the hub file's directory if no common prefix.
|
|
165
|
+
*/
|
|
166
|
+
function computeClusterName(paths) {
|
|
167
|
+
if (paths.length === 0)
|
|
168
|
+
return "unknown";
|
|
169
|
+
if (paths.length === 1) {
|
|
170
|
+
const parts = paths[0].split("/");
|
|
171
|
+
return parts.slice(0, -1).join("/") || paths[0];
|
|
172
|
+
}
|
|
173
|
+
// Find common directory prefix
|
|
174
|
+
const segments = paths.map(p => p.split("/"));
|
|
175
|
+
const minLen = Math.min(...segments.map(s => s.length));
|
|
176
|
+
const common = [];
|
|
177
|
+
for (let i = 0; i < minLen - 1; i++) {
|
|
178
|
+
const seg = segments[0][i];
|
|
179
|
+
if (segments.every(s => s[i] === seg)) {
|
|
180
|
+
common.push(seg);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// If common prefix is too short (e.g., just "src"), try to find a
|
|
187
|
+
// majority directory that better describes the cluster
|
|
188
|
+
if (common.length <= 1 && segments[0].length > 2) {
|
|
189
|
+
const dirCounts = new Map();
|
|
190
|
+
for (const seg of segments) {
|
|
191
|
+
// Use the first 2-3 directory segments as the "directory identity"
|
|
192
|
+
const dir = seg.slice(0, Math.min(seg.length - 1, 3)).join("/");
|
|
193
|
+
dirCounts.set(dir, (dirCounts.get(dir) || 0) + 1);
|
|
194
|
+
}
|
|
195
|
+
let bestDir = "";
|
|
196
|
+
let bestCount = 0;
|
|
197
|
+
for (const [dir, count] of dirCounts) {
|
|
198
|
+
if (count > bestCount) {
|
|
199
|
+
bestCount = count;
|
|
200
|
+
bestDir = dir;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (bestDir && bestCount > paths.length / 2) {
|
|
204
|
+
return bestDir;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return common.length > 0 ? common.join("/") : paths[0].split("/").slice(0, -1).join("/") || "root";
|
|
208
|
+
}
|
|
209
|
+
/** Fisher-Yates shuffle in place. */
|
|
210
|
+
function shuffleInPlace(arr) {
|
|
211
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
212
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
213
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=cluster.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cluster.js","sourceRoot":"","sources":["../../../src/search/cluster.ts"],"names":[],"mappings":"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"}
|