devlensio 0.2.0 → 0.4.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/graph/traversal.d.ts +38 -0
- package/dist/graph/traversal.js +99 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +10 -2
- package/package.json +1 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { CycleGroup } from "../summarizer/types.js";
|
|
2
|
+
import { CodeEdge, CodeNode, EdgeType } from "../types.js";
|
|
3
|
+
export interface GraphIndex {
|
|
4
|
+
nodesById: Map<string, CodeNode>;
|
|
5
|
+
forward: Map<string, CodeEdge[]>;
|
|
6
|
+
reverse: Map<string, CodeEdge[]>;
|
|
7
|
+
nodesByFilePath: Map<string, string[]>;
|
|
8
|
+
}
|
|
9
|
+
export declare function buildGraphIndex(nodes: CodeNode[], edges: CodeEdge[]): GraphIndex;
|
|
10
|
+
export interface TraversalHit {
|
|
11
|
+
nodeId: string;
|
|
12
|
+
hop: number;
|
|
13
|
+
viaEdge: EdgeType;
|
|
14
|
+
}
|
|
15
|
+
export interface TraversalResult {
|
|
16
|
+
seedId: string;
|
|
17
|
+
direction: "upstream" | "downstream";
|
|
18
|
+
hits: TraversalHit[];
|
|
19
|
+
truncated: boolean;
|
|
20
|
+
stoppedAtRadius: number;
|
|
21
|
+
hop1Count: number;
|
|
22
|
+
radiusUsed: number;
|
|
23
|
+
radiusWasExplicit: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface TraversalOpts {
|
|
26
|
+
radius?: number;
|
|
27
|
+
edgeTypes?: EdgeType[];
|
|
28
|
+
}
|
|
29
|
+
export declare function getBlastRadius(index: GraphIndex, seedId: string, opts?: TraversalOpts): TraversalResult;
|
|
30
|
+
export declare function getKHop(index: GraphIndex, seedId: string, opts?: TraversalOpts): TraversalResult;
|
|
31
|
+
export interface SubgraphResult {
|
|
32
|
+
seedNodeId: string;
|
|
33
|
+
clusterId: string;
|
|
34
|
+
nodes: CodeNode[];
|
|
35
|
+
edges: CodeEdge[];
|
|
36
|
+
}
|
|
37
|
+
export declare function getSubgraph(allNodes: CodeNode[], allEdges: CodeEdge[], nodeScores: Record<string, number>, seedNodeId: string): SubgraphResult | undefined;
|
|
38
|
+
export declare function findCycles(nodes: CodeNode[], edges: CodeEdge[]): CycleGroup[];
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { computeClusters } from "../clustering/index.js";
|
|
2
|
+
import { buildTopologicalOrder } from "../summarizer/topological.js";
|
|
3
|
+
export function buildGraphIndex(nodes, edges) {
|
|
4
|
+
const nodesById = new Map();
|
|
5
|
+
const forward = new Map();
|
|
6
|
+
const reverse = new Map();
|
|
7
|
+
const nodesByFilePath = new Map();
|
|
8
|
+
for (const node of nodes) {
|
|
9
|
+
nodesById.set(node.id, node);
|
|
10
|
+
const fp = node.filePath || "external";
|
|
11
|
+
if (!nodesByFilePath.has(fp))
|
|
12
|
+
nodesByFilePath.set(fp, []);
|
|
13
|
+
nodesByFilePath.get(fp).push(node.id);
|
|
14
|
+
}
|
|
15
|
+
for (const edge of edges) {
|
|
16
|
+
if (!forward.has(edge.from))
|
|
17
|
+
forward.set(edge.from, []);
|
|
18
|
+
forward.get(edge.from).push(edge);
|
|
19
|
+
if (!reverse.has(edge.to))
|
|
20
|
+
reverse.set(edge.to, []);
|
|
21
|
+
reverse.get(edge.to).push(edge);
|
|
22
|
+
}
|
|
23
|
+
return { nodesById, forward, reverse, nodesByFilePath };
|
|
24
|
+
}
|
|
25
|
+
const DEFAULT_RADIUS = 2;
|
|
26
|
+
const HOP1_CAP = 100;
|
|
27
|
+
function bfsTraverse(index, seedId, direction, opts = {}) {
|
|
28
|
+
const explicit = opts.radius !== undefined;
|
|
29
|
+
const maxRadius = opts.radius ?? DEFAULT_RADIUS;
|
|
30
|
+
const edgeFilter = opts.edgeTypes ? new Set(opts.edgeTypes) : null;
|
|
31
|
+
// downstream (callees) walks forward edges; upstream (callers) walks reverse
|
|
32
|
+
const adj = direction === "downstream" ? index.forward : index.reverse;
|
|
33
|
+
const visited = new Set([seedId]); // seed excluded from results
|
|
34
|
+
const hits = [];
|
|
35
|
+
let queue = [seedId];
|
|
36
|
+
let hop1Count = 0;
|
|
37
|
+
let truncated = false;
|
|
38
|
+
let stoppedAtRadius = 0;
|
|
39
|
+
for (let hop = 1; hop <= maxRadius; hop++) {
|
|
40
|
+
const nextqueue = [];
|
|
41
|
+
for (const nodeId of queue) {
|
|
42
|
+
for (const edge of adj.get(nodeId) ?? []) {
|
|
43
|
+
if (edgeFilter && !edgeFilter.has(edge.type))
|
|
44
|
+
continue;
|
|
45
|
+
const neighborId = direction === "downstream" ? edge.to : edge.from;
|
|
46
|
+
if (visited.has(neighborId))
|
|
47
|
+
continue;
|
|
48
|
+
visited.add(neighborId);
|
|
49
|
+
hits.push({ nodeId: neighborId, hop, viaEdge: edge.type });
|
|
50
|
+
nextqueue.push(neighborId);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (hop === 1) {
|
|
54
|
+
hop1Count = nextqueue.length;
|
|
55
|
+
// Cap applies ONLY on the default (non-explicit) path:
|
|
56
|
+
// huge fanout → return hop-1 hits, skip deeper hops, flag truncated.
|
|
57
|
+
if (!explicit && hop1Count >= HOP1_CAP) {
|
|
58
|
+
truncated = true;
|
|
59
|
+
stoppedAtRadius = 1;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
stoppedAtRadius = hop;
|
|
64
|
+
queue = nextqueue;
|
|
65
|
+
if (queue.length === 0)
|
|
66
|
+
break; // nothing more to expand
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
seedId, direction, hits, truncated, stoppedAtRadius, hop1Count,
|
|
70
|
+
radiusUsed: maxRadius, radiusWasExplicit: explicit,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// Upstream / dependents — "if I change this node, what is affected"
|
|
74
|
+
export function getBlastRadius(index, seedId, opts) {
|
|
75
|
+
return bfsTraverse(index, seedId, "upstream", opts);
|
|
76
|
+
}
|
|
77
|
+
// Downstream / dependencies — "what this node calls/uses"
|
|
78
|
+
export function getKHop(index, seedId, opts) {
|
|
79
|
+
return bfsTraverse(index, seedId, "downstream", opts);
|
|
80
|
+
}
|
|
81
|
+
export function getSubgraph(allNodes, allEdges, nodeScores, seedNodeId) {
|
|
82
|
+
const { clusterMembership } = computeClusters(allNodes, allEdges, nodeScores);
|
|
83
|
+
const clusterId = clusterMembership[seedNodeId];
|
|
84
|
+
if (!clusterId)
|
|
85
|
+
return undefined; // seed not found / not clustered
|
|
86
|
+
const memberIds = new Set();
|
|
87
|
+
for (const [nodeId, cId] of Object.entries(clusterMembership)) {
|
|
88
|
+
if (cId === clusterId)
|
|
89
|
+
memberIds.add(nodeId);
|
|
90
|
+
}
|
|
91
|
+
const nodes = allNodes.filter(n => memberIds.has(n.id));
|
|
92
|
+
const edges = allEdges.filter(e => memberIds.has(e.from) && memberIds.has(e.to));
|
|
93
|
+
return { seedNodeId, clusterId, nodes, edges };
|
|
94
|
+
}
|
|
95
|
+
// Cyclic dependency groups — reuses the connected-component detection that
|
|
96
|
+
// already runs inside the topological sort (src/summarizer/topological.ts).
|
|
97
|
+
export function findCycles(nodes, edges) {
|
|
98
|
+
return buildTopologicalOrder(nodes, edges).cycleGroups;
|
|
99
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,9 +7,14 @@ export * from "./summarizer/types.js";
|
|
|
7
7
|
export * from "./clustering/index.js";
|
|
8
8
|
export * from "./pipeline/index.js";
|
|
9
9
|
export * from "./config/types.js";
|
|
10
|
+
export { queue } from "./jobs/index.js";
|
|
11
|
+
export { storage } from "./storage/index.js";
|
|
12
|
+
export { resolveConfig, initConfig, maskConfig, writeConfig } from "./config/index.js";
|
|
13
|
+
export { readPackageDependencies, categorizeLibrary } from "./graph/thirdPartyLibs.js";
|
|
10
14
|
export { analyzePipeline } from "./pipeline/index.js";
|
|
11
15
|
export { runSummarization } from "./summarizer/index.js";
|
|
12
|
-
export { resolveConfig } from "./config/index.js";
|
|
13
16
|
export { computeClusters } from "./clustering/index.js";
|
|
14
17
|
export { EDGE_LABELS } from "./summarizer/prompts.js";
|
|
15
18
|
export { CycleGroup, TopologicalResult } from "./summarizer/types.js";
|
|
19
|
+
export { buildGraphIndex, getBlastRadius, getKHop, getSubgraph, findCycles, } from "./graph/traversal.js";
|
|
20
|
+
export type { GraphIndex, TraversalHit, TraversalResult, TraversalOpts, SubgraphResult, } from "./graph/traversal.js";
|
package/dist/index.js
CHANGED
|
@@ -7,9 +7,17 @@ export * from "./summarizer/types.js";
|
|
|
7
7
|
export * from "./clustering/index.js";
|
|
8
8
|
export * from "./pipeline/index.js";
|
|
9
9
|
export * from "./config/types.js";
|
|
10
|
-
//
|
|
10
|
+
// Singletons — server handlers import these directly
|
|
11
|
+
export { queue } from "./jobs/index.js";
|
|
12
|
+
export { storage } from "./storage/index.js";
|
|
13
|
+
// Config helpers
|
|
14
|
+
export { resolveConfig, initConfig, maskConfig, writeConfig } from "./config/index.js";
|
|
15
|
+
// Pre-scan helpers
|
|
16
|
+
export { readPackageDependencies, categorizeLibrary } from "./graph/thirdPartyLibs.js";
|
|
17
|
+
// Pipeline & analysis
|
|
11
18
|
export { analyzePipeline } from "./pipeline/index.js";
|
|
12
19
|
export { runSummarization } from "./summarizer/index.js";
|
|
13
|
-
export { resolveConfig } from "./config/index.js";
|
|
14
20
|
export { computeClusters } from "./clustering/index.js";
|
|
15
21
|
export { EDGE_LABELS } from "./summarizer/prompts.js";
|
|
22
|
+
// Graph traversal & queries (consumed by the MCP server)
|
|
23
|
+
export { buildGraphIndex, getBlastRadius, getKHop, getSubgraph, findCycles, } from "./graph/traversal.js";
|