depwire-cli 0.1.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/LICENSE +21 -0
- package/README.md +178 -0
- package/dist/chunk-2XOJSBSD.js +3302 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +243 -0
- package/dist/mcpb-entry.d.ts +1 -0
- package/dist/mcpb-entry.js +70 -0
- package/dist/viz/public/arc.js +579 -0
- package/dist/viz/public/index.html +48 -0
- package/dist/viz/public/style.css +326 -0
- package/icon.png +0 -0
- package/package.json +77 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildGraph,
|
|
4
|
+
createEmptyState,
|
|
5
|
+
getArchitectureSummary,
|
|
6
|
+
getImpact,
|
|
7
|
+
parseProject,
|
|
8
|
+
prepareVizData,
|
|
9
|
+
searchSymbols,
|
|
10
|
+
startMcpServer,
|
|
11
|
+
startVizServer,
|
|
12
|
+
updateFileInGraph,
|
|
13
|
+
watchProject
|
|
14
|
+
} from "./chunk-2XOJSBSD.js";
|
|
15
|
+
|
|
16
|
+
// src/index.ts
|
|
17
|
+
import { Command } from "commander";
|
|
18
|
+
import { resolve } from "path";
|
|
19
|
+
import { writeFileSync, readFileSync, existsSync } from "fs";
|
|
20
|
+
|
|
21
|
+
// src/graph/serializer.ts
|
|
22
|
+
import { DirectedGraph } from "graphology";
|
|
23
|
+
function exportToJSON(graph, projectRoot) {
|
|
24
|
+
const nodes = [];
|
|
25
|
+
const edges = [];
|
|
26
|
+
const fileSet = /* @__PURE__ */ new Set();
|
|
27
|
+
graph.forEachNode((nodeId, attrs) => {
|
|
28
|
+
nodes.push({
|
|
29
|
+
id: nodeId,
|
|
30
|
+
name: attrs.name,
|
|
31
|
+
kind: attrs.kind,
|
|
32
|
+
filePath: attrs.filePath,
|
|
33
|
+
startLine: attrs.startLine,
|
|
34
|
+
endLine: attrs.endLine,
|
|
35
|
+
exported: attrs.exported,
|
|
36
|
+
scope: attrs.scope
|
|
37
|
+
});
|
|
38
|
+
fileSet.add(attrs.filePath);
|
|
39
|
+
});
|
|
40
|
+
graph.forEachEdge((edge, attrs, source, target) => {
|
|
41
|
+
edges.push({
|
|
42
|
+
source,
|
|
43
|
+
target,
|
|
44
|
+
kind: attrs.kind,
|
|
45
|
+
filePath: attrs.filePath,
|
|
46
|
+
line: attrs.line
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
projectRoot,
|
|
51
|
+
files: Array.from(fileSet).sort(),
|
|
52
|
+
nodes,
|
|
53
|
+
edges,
|
|
54
|
+
metadata: {
|
|
55
|
+
parsedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
56
|
+
fileCount: fileSet.size,
|
|
57
|
+
nodeCount: nodes.length,
|
|
58
|
+
edgeCount: edges.length
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function importFromJSON(json) {
|
|
63
|
+
const graph = new DirectedGraph();
|
|
64
|
+
for (const node of json.nodes) {
|
|
65
|
+
graph.addNode(node.id, {
|
|
66
|
+
name: node.name,
|
|
67
|
+
kind: node.kind,
|
|
68
|
+
filePath: node.filePath,
|
|
69
|
+
startLine: node.startLine,
|
|
70
|
+
endLine: node.endLine,
|
|
71
|
+
exported: node.exported,
|
|
72
|
+
scope: node.scope
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
for (const edge of json.edges) {
|
|
76
|
+
if (graph.hasNode(edge.source) && graph.hasNode(edge.target)) {
|
|
77
|
+
graph.mergeEdge(edge.source, edge.target, {
|
|
78
|
+
kind: edge.kind,
|
|
79
|
+
filePath: edge.filePath,
|
|
80
|
+
line: edge.line
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return graph;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/index.ts
|
|
88
|
+
var program = new Command();
|
|
89
|
+
program.name("depwire").description("Code cross-reference graph builder for TypeScript projects").version("0.1.0");
|
|
90
|
+
program.command("parse").description("Parse a TypeScript project and build dependency graph").argument("<directory>", "Project directory to parse").option("-o, --output <path>", "Output JSON file path", "depwire-output.json").option("--pretty", "Pretty-print JSON output").option("--stats", "Print summary statistics").action(async (directory, options) => {
|
|
91
|
+
const startTime = Date.now();
|
|
92
|
+
try {
|
|
93
|
+
const projectRoot = resolve(directory);
|
|
94
|
+
console.log(`Parsing project: ${projectRoot}`);
|
|
95
|
+
const parsedFiles = parseProject(projectRoot);
|
|
96
|
+
console.log(`Parsed ${parsedFiles.length} files`);
|
|
97
|
+
const graph = buildGraph(parsedFiles);
|
|
98
|
+
const projectGraph = exportToJSON(graph, projectRoot);
|
|
99
|
+
const json = options.pretty ? JSON.stringify(projectGraph, null, 2) : JSON.stringify(projectGraph);
|
|
100
|
+
writeFileSync(options.output, json, "utf-8");
|
|
101
|
+
console.log(`Graph exported to: ${options.output}`);
|
|
102
|
+
if (options.stats) {
|
|
103
|
+
const elapsed = Date.now() - startTime;
|
|
104
|
+
const summary = getArchitectureSummary(graph);
|
|
105
|
+
console.log("\n=== Project Statistics ===");
|
|
106
|
+
console.log(`Files: ${summary.fileCount}`);
|
|
107
|
+
console.log(`Symbols: ${summary.symbolCount}`);
|
|
108
|
+
console.log(`Edges: ${summary.edgeCount}`);
|
|
109
|
+
console.log(`Time: ${elapsed}ms`);
|
|
110
|
+
if (summary.mostConnectedFiles.length > 0) {
|
|
111
|
+
console.log("\nMost Connected Files:");
|
|
112
|
+
for (const file of summary.mostConnectedFiles.slice(0, 5)) {
|
|
113
|
+
console.log(` ${file.filePath} (${file.connections} connections)`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (summary.orphanFiles.length > 0) {
|
|
117
|
+
console.log(`
|
|
118
|
+
Orphan Files (no cross-references): ${summary.orphanFiles.length}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} catch (err) {
|
|
122
|
+
console.error("Error parsing project:", err);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
program.command("query").description("Query impact analysis for a symbol").argument("<directory>", "Project directory").argument("<symbol-name>", "Symbol name to query").action(async (directory, symbolName) => {
|
|
127
|
+
try {
|
|
128
|
+
const projectRoot = resolve(directory);
|
|
129
|
+
const cacheFile = "depwire-output.json";
|
|
130
|
+
let graph;
|
|
131
|
+
if (existsSync(cacheFile)) {
|
|
132
|
+
console.log("Loading from cache...");
|
|
133
|
+
const json = JSON.parse(readFileSync(cacheFile, "utf-8"));
|
|
134
|
+
graph = importFromJSON(json);
|
|
135
|
+
} else {
|
|
136
|
+
console.log("Parsing project...");
|
|
137
|
+
const parsedFiles = parseProject(projectRoot);
|
|
138
|
+
graph = buildGraph(parsedFiles);
|
|
139
|
+
}
|
|
140
|
+
const matches = searchSymbols(graph, symbolName);
|
|
141
|
+
if (matches.length === 0) {
|
|
142
|
+
console.log(`No symbols found matching: ${symbolName}`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (matches.length > 1) {
|
|
146
|
+
console.log(`Found ${matches.length} symbols matching "${symbolName}":`);
|
|
147
|
+
for (const match of matches) {
|
|
148
|
+
console.log(` - ${match.name} (${match.kind}) in ${match.filePath}:${match.startLine}`);
|
|
149
|
+
}
|
|
150
|
+
console.log("\nShowing impact for all matches...\n");
|
|
151
|
+
}
|
|
152
|
+
for (const match of matches) {
|
|
153
|
+
console.log(`=== Impact Analysis: ${match.name} (${match.kind}) ===`);
|
|
154
|
+
console.log(`Location: ${match.filePath}:${match.startLine}-${match.endLine}`);
|
|
155
|
+
const impact = getImpact(graph, match.id);
|
|
156
|
+
console.log(`
|
|
157
|
+
Direct Dependents: ${impact.directDependents.length}`);
|
|
158
|
+
for (const dep of impact.directDependents) {
|
|
159
|
+
console.log(` - ${dep.name} (${dep.kind}) in ${dep.filePath}:${dep.startLine}`);
|
|
160
|
+
}
|
|
161
|
+
console.log(`
|
|
162
|
+
Total Transitive Dependents: ${impact.transitiveDependents.length}`);
|
|
163
|
+
console.log(`Affected Files: ${impact.affectedFiles.length}`);
|
|
164
|
+
for (const file of impact.affectedFiles) {
|
|
165
|
+
console.log(` - ${file}`);
|
|
166
|
+
}
|
|
167
|
+
console.log("");
|
|
168
|
+
}
|
|
169
|
+
} catch (err) {
|
|
170
|
+
console.error("Error querying symbol:", err);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
program.command("viz").description("Launch interactive arc diagram visualization").argument("<directory>", "Project directory to visualize").option("-p, --port <number>", "Server port", "3333").option("--no-open", "Don't auto-open browser").action(async (directory, options) => {
|
|
175
|
+
try {
|
|
176
|
+
const projectRoot = resolve(directory);
|
|
177
|
+
console.log(`Parsing project: ${projectRoot}`);
|
|
178
|
+
const parsedFiles = parseProject(projectRoot);
|
|
179
|
+
console.log(`Parsed ${parsedFiles.length} files`);
|
|
180
|
+
const graph = buildGraph(parsedFiles);
|
|
181
|
+
const vizData = prepareVizData(graph, projectRoot);
|
|
182
|
+
console.log(`Found ${vizData.stats.totalSymbols} symbols, ${vizData.stats.totalCrossFileEdges} cross-file edges`);
|
|
183
|
+
const port = parseInt(options.port, 10);
|
|
184
|
+
startVizServer(vizData, graph, projectRoot, port, options.open);
|
|
185
|
+
} catch (err) {
|
|
186
|
+
console.error("Error starting visualization:", err);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
program.command("mcp").description("Start MCP server for AI coding tools").argument("[directory]", "Project directory to analyze (optional - use connect_repo tool to connect later)").action(async (directory) => {
|
|
191
|
+
try {
|
|
192
|
+
const state = createEmptyState();
|
|
193
|
+
if (directory) {
|
|
194
|
+
const projectRoot = resolve(directory);
|
|
195
|
+
console.error(`Parsing project: ${projectRoot}`);
|
|
196
|
+
const parsedFiles = parseProject(projectRoot);
|
|
197
|
+
console.error(`Parsed ${parsedFiles.length} files`);
|
|
198
|
+
const graph = buildGraph(parsedFiles);
|
|
199
|
+
console.error(`Built graph: ${graph.order} symbols, ${graph.size} edges`);
|
|
200
|
+
state.graph = graph;
|
|
201
|
+
state.projectRoot = projectRoot;
|
|
202
|
+
state.projectName = projectRoot.split("/").pop() || "project";
|
|
203
|
+
console.error("Starting file watcher...");
|
|
204
|
+
state.watcher = watchProject(projectRoot, {
|
|
205
|
+
onFileChanged: async (filePath) => {
|
|
206
|
+
console.error(`File changed: ${filePath}`);
|
|
207
|
+
try {
|
|
208
|
+
await updateFileInGraph(state.graph, projectRoot, filePath);
|
|
209
|
+
console.error(`Graph updated for ${filePath}`);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error(`Failed to update graph: ${error}`);
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
onFileAdded: async (filePath) => {
|
|
215
|
+
console.error(`File added: ${filePath}`);
|
|
216
|
+
try {
|
|
217
|
+
await updateFileInGraph(state.graph, projectRoot, filePath);
|
|
218
|
+
console.error(`Graph updated for ${filePath}`);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error(`Failed to update graph: ${error}`);
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
onFileDeleted: (filePath) => {
|
|
224
|
+
console.error(`File deleted: ${filePath}`);
|
|
225
|
+
try {
|
|
226
|
+
const fileNodes = state.graph.filterNodes(
|
|
227
|
+
(node, attrs) => attrs.filePath === filePath
|
|
228
|
+
);
|
|
229
|
+
fileNodes.forEach((node) => state.graph.dropNode(node));
|
|
230
|
+
console.error(`Removed ${filePath} from graph`);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.error(`Failed to remove file: ${error}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
await startMcpServer(state);
|
|
238
|
+
} catch (err) {
|
|
239
|
+
console.error("Error starting MCP server:", err);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
program.parse();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildGraph,
|
|
4
|
+
createEmptyState,
|
|
5
|
+
parseProject,
|
|
6
|
+
startMcpServer,
|
|
7
|
+
updateFileInGraph,
|
|
8
|
+
watchProject
|
|
9
|
+
} from "./chunk-2XOJSBSD.js";
|
|
10
|
+
|
|
11
|
+
// src/mcpb-entry.ts
|
|
12
|
+
import { resolve } from "path";
|
|
13
|
+
async function main() {
|
|
14
|
+
const state = createEmptyState();
|
|
15
|
+
const projectPath = process.env.MCPB_CONFIG_PROJECT_PATH || process.env.DEPWIRE_PROJECT_PATH || "";
|
|
16
|
+
if (projectPath) {
|
|
17
|
+
try {
|
|
18
|
+
const projectRoot = resolve(projectPath);
|
|
19
|
+
console.error(`[MCPB] Parsing project: ${projectRoot}`);
|
|
20
|
+
const parsedFiles = parseProject(projectRoot);
|
|
21
|
+
console.error(`[MCPB] Parsed ${parsedFiles.length} files`);
|
|
22
|
+
const graph = buildGraph(parsedFiles);
|
|
23
|
+
console.error(`[MCPB] Built graph: ${graph.order} symbols, ${graph.size} edges`);
|
|
24
|
+
state.graph = graph;
|
|
25
|
+
state.projectRoot = projectRoot;
|
|
26
|
+
state.projectName = projectRoot.split("/").pop() || "project";
|
|
27
|
+
console.error("[MCPB] Starting file watcher...");
|
|
28
|
+
state.watcher = watchProject(projectRoot, {
|
|
29
|
+
onFileChanged: async (filePath) => {
|
|
30
|
+
console.error(`[MCPB] File changed: ${filePath}`);
|
|
31
|
+
try {
|
|
32
|
+
await updateFileInGraph(state.graph, projectRoot, filePath);
|
|
33
|
+
console.error(`[MCPB] Graph updated for ${filePath}`);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error(`[MCPB] Failed to update graph: ${error}`);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
onFileAdded: async (filePath) => {
|
|
39
|
+
console.error(`[MCPB] File added: ${filePath}`);
|
|
40
|
+
try {
|
|
41
|
+
await updateFileInGraph(state.graph, projectRoot, filePath);
|
|
42
|
+
console.error(`[MCPB] Graph updated for ${filePath}`);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error(`[MCPB] Failed to update graph: ${error}`);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
onFileDeleted: (filePath) => {
|
|
48
|
+
console.error(`[MCPB] File deleted: ${filePath}`);
|
|
49
|
+
try {
|
|
50
|
+
const fileNodes = state.graph.filterNodes(
|
|
51
|
+
(node, attrs) => attrs.filePath === filePath
|
|
52
|
+
);
|
|
53
|
+
fileNodes.forEach((node) => state.graph.dropNode(node));
|
|
54
|
+
console.error(`[MCPB] Removed ${filePath} from graph`);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error(`[MCPB] Failed to remove file: ${error}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error(`[MCPB] Failed to parse project: ${error}`);
|
|
62
|
+
console.error("[MCPB] Starting without a project loaded. Use connect_repo to connect.");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
await startMcpServer(state);
|
|
66
|
+
}
|
|
67
|
+
main().catch((err) => {
|
|
68
|
+
console.error("[MCPB] Fatal error:", err);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
});
|