depwire-cli 0.6.1 → 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/README.md +73 -40
- package/dist/{chunk-S3RUBXRF.js → chunk-65H7HCM4.js} +428 -15
- package/dist/index.js +318 -26
- package/dist/mcpb-entry.js +1 -1
- package/dist/viz/public/temporal.css +397 -0
- package/dist/viz/public/temporal.html +118 -0
- package/dist/viz/public/temporal.js +475 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,25 +2,37 @@
|
|
|
2
2
|
import {
|
|
3
3
|
buildGraph,
|
|
4
4
|
calculateHealthScore,
|
|
5
|
+
checkoutCommit,
|
|
5
6
|
createEmptyState,
|
|
7
|
+
createSnapshot,
|
|
8
|
+
findProjectRoot,
|
|
6
9
|
generateDocs,
|
|
7
10
|
getArchitectureSummary,
|
|
11
|
+
getCommitLog,
|
|
12
|
+
getCurrentBranch,
|
|
8
13
|
getHealthTrend,
|
|
9
14
|
getImpact,
|
|
15
|
+
isGitRepo,
|
|
16
|
+
loadSnapshot,
|
|
10
17
|
parseProject,
|
|
18
|
+
popStash,
|
|
11
19
|
prepareVizData,
|
|
20
|
+
restoreOriginal,
|
|
21
|
+
sampleCommits,
|
|
22
|
+
saveSnapshot,
|
|
12
23
|
searchSymbols,
|
|
13
24
|
startMcpServer,
|
|
14
25
|
startVizServer,
|
|
26
|
+
stashChanges,
|
|
15
27
|
updateFileInGraph,
|
|
16
28
|
watchProject
|
|
17
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-65H7HCM4.js";
|
|
18
30
|
|
|
19
31
|
// src/index.ts
|
|
20
32
|
import { Command } from "commander";
|
|
21
|
-
import { resolve, dirname, join } from "path";
|
|
22
|
-
import { writeFileSync, readFileSync, existsSync } from "fs";
|
|
23
|
-
import { fileURLToPath } from "url";
|
|
33
|
+
import { resolve, dirname as dirname2, join as join3 } from "path";
|
|
34
|
+
import { writeFileSync, readFileSync as readFileSync2, existsSync } from "fs";
|
|
35
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
24
36
|
|
|
25
37
|
// src/graph/serializer.ts
|
|
26
38
|
import { DirectedGraph } from "graphology";
|
|
@@ -206,16 +218,271 @@ function gray(text) {
|
|
|
206
218
|
// src/index.ts
|
|
207
219
|
import { readFileSync as readFileSyncNode, appendFileSync, existsSync as existsSyncNode } from "fs";
|
|
208
220
|
import { createInterface } from "readline";
|
|
221
|
+
|
|
222
|
+
// src/temporal/index.ts
|
|
223
|
+
import { join as join2 } from "path";
|
|
224
|
+
|
|
225
|
+
// src/viz/temporal-server.ts
|
|
226
|
+
import express from "express";
|
|
227
|
+
import { readFileSync } from "fs";
|
|
228
|
+
import { fileURLToPath } from "url";
|
|
229
|
+
import { dirname, join } from "path";
|
|
230
|
+
import open from "open";
|
|
231
|
+
|
|
232
|
+
// src/viz/temporal-data.ts
|
|
233
|
+
import { basename } from "path";
|
|
234
|
+
|
|
235
|
+
// src/temporal/diff.ts
|
|
236
|
+
function diffSnapshots(previous, current) {
|
|
237
|
+
const prevFiles = new Set(previous.files.map((f) => f.path));
|
|
238
|
+
const currFiles = new Set(current.files.map((f) => f.path));
|
|
239
|
+
const addedFiles = Array.from(currFiles).filter((f) => !prevFiles.has(f));
|
|
240
|
+
const removedFiles = Array.from(prevFiles).filter((f) => !currFiles.has(f));
|
|
241
|
+
const prevEdges = new Set(
|
|
242
|
+
previous.edges.map((e) => `${e.source}|${e.target}`)
|
|
243
|
+
);
|
|
244
|
+
const currEdges = new Set(current.edges.map((e) => `${e.source}|${e.target}`));
|
|
245
|
+
const addedEdgeKeys = Array.from(currEdges).filter((e) => !prevEdges.has(e));
|
|
246
|
+
const removedEdgeKeys = Array.from(prevEdges).filter((e) => !currEdges.has(e));
|
|
247
|
+
const addedEdges = addedEdgeKeys.map((key) => {
|
|
248
|
+
const [source, target] = key.split("|");
|
|
249
|
+
return { source, target };
|
|
250
|
+
});
|
|
251
|
+
const removedEdges = removedEdgeKeys.map((key) => {
|
|
252
|
+
const [source, target] = key.split("|");
|
|
253
|
+
return { source, target };
|
|
254
|
+
});
|
|
255
|
+
return {
|
|
256
|
+
addedFiles,
|
|
257
|
+
removedFiles,
|
|
258
|
+
addedEdges,
|
|
259
|
+
removedEdges,
|
|
260
|
+
statsChange: {
|
|
261
|
+
files: current.stats.totalFiles - previous.stats.totalFiles,
|
|
262
|
+
symbols: current.stats.totalSymbols - previous.stats.totalSymbols,
|
|
263
|
+
edges: current.stats.totalEdges - previous.stats.totalEdges
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/viz/temporal-data.ts
|
|
269
|
+
function prepareTemporalVizData(snapshots, projectRoot) {
|
|
270
|
+
const projectName = basename(projectRoot);
|
|
271
|
+
const snapshotsWithDiff = snapshots.map((snapshot, index) => {
|
|
272
|
+
const diff = index > 0 ? diffSnapshots(snapshots[index - 1], snapshot) : void 0;
|
|
273
|
+
return {
|
|
274
|
+
commitHash: snapshot.commitHash,
|
|
275
|
+
commitDate: snapshot.commitDate,
|
|
276
|
+
commitMessage: snapshot.commitMessage,
|
|
277
|
+
commitAuthor: snapshot.commitAuthor,
|
|
278
|
+
stats: snapshot.stats,
|
|
279
|
+
files: snapshot.files,
|
|
280
|
+
arcs: snapshot.edges,
|
|
281
|
+
diff
|
|
282
|
+
};
|
|
283
|
+
});
|
|
284
|
+
const timeline = snapshots.map((snapshot, index) => ({
|
|
285
|
+
index,
|
|
286
|
+
date: snapshot.commitDate,
|
|
287
|
+
shortHash: snapshot.commitHash.substring(0, 8),
|
|
288
|
+
message: snapshot.commitMessage
|
|
289
|
+
}));
|
|
290
|
+
return {
|
|
291
|
+
projectName,
|
|
292
|
+
snapshots: snapshotsWithDiff,
|
|
293
|
+
timeline
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/viz/temporal-server.ts
|
|
209
298
|
var __filename = fileURLToPath(import.meta.url);
|
|
210
299
|
var __dirname = dirname(__filename);
|
|
211
|
-
|
|
212
|
-
|
|
300
|
+
async function findAvailablePort(startPort) {
|
|
301
|
+
const net = await import("net");
|
|
302
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
303
|
+
const testPort = startPort + attempt;
|
|
304
|
+
const isAvailable = await new Promise((resolve2) => {
|
|
305
|
+
const server = net.createServer().once("error", () => resolve2(false)).once("listening", () => {
|
|
306
|
+
server.close();
|
|
307
|
+
resolve2(true);
|
|
308
|
+
}).listen(testPort, "127.0.0.1");
|
|
309
|
+
});
|
|
310
|
+
if (isAvailable) {
|
|
311
|
+
return testPort;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
throw new Error(`No available port found starting from ${startPort}`);
|
|
315
|
+
}
|
|
316
|
+
async function startTemporalServer(snapshots, projectRoot, preferredPort = 3334) {
|
|
317
|
+
const availablePort = await findAvailablePort(preferredPort);
|
|
318
|
+
const app = express();
|
|
319
|
+
const vizData = prepareTemporalVizData(snapshots, projectRoot);
|
|
320
|
+
app.get("/api/data", (_req, res) => {
|
|
321
|
+
res.json(vizData);
|
|
322
|
+
});
|
|
323
|
+
const publicDir = join(__dirname, "viz", "public");
|
|
324
|
+
app.get("/", (_req, res) => {
|
|
325
|
+
const htmlPath = join(publicDir, "temporal.html");
|
|
326
|
+
const html = readFileSync(htmlPath, "utf-8");
|
|
327
|
+
res.send(html);
|
|
328
|
+
});
|
|
329
|
+
app.get("/temporal.js", (_req, res) => {
|
|
330
|
+
const jsPath = join(publicDir, "temporal.js");
|
|
331
|
+
const js = readFileSync(jsPath, "utf-8");
|
|
332
|
+
res.type("application/javascript").send(js);
|
|
333
|
+
});
|
|
334
|
+
app.get("/temporal.css", (_req, res) => {
|
|
335
|
+
const cssPath = join(publicDir, "temporal.css");
|
|
336
|
+
const css = readFileSync(cssPath, "utf-8");
|
|
337
|
+
res.type("text/css").send(css);
|
|
338
|
+
});
|
|
339
|
+
const server = app.listen(availablePort, "127.0.0.1", () => {
|
|
340
|
+
const url = `http://127.0.0.1:${availablePort}`;
|
|
341
|
+
console.log(`
|
|
342
|
+
\u2713 Temporal visualization server running at ${url}`);
|
|
343
|
+
console.log(" Press Ctrl+C to stop\n");
|
|
344
|
+
open(url).catch(() => {
|
|
345
|
+
console.log(" (Could not open browser automatically)");
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
await new Promise((resolve2, reject) => {
|
|
349
|
+
server.on("error", reject);
|
|
350
|
+
process.on("SIGINT", () => {
|
|
351
|
+
console.log("\n\nShutting down temporal server...");
|
|
352
|
+
server.close(() => {
|
|
353
|
+
console.log("Server stopped");
|
|
354
|
+
resolve2();
|
|
355
|
+
process.exit(0);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/temporal/index.ts
|
|
362
|
+
async function runTemporalAnalysis(projectDir, options) {
|
|
363
|
+
if (!isGitRepo(projectDir)) {
|
|
364
|
+
throw new Error("Not a git repository. Temporal analysis requires git history.");
|
|
365
|
+
}
|
|
366
|
+
console.log("\u{1F50D} Analyzing git history...");
|
|
367
|
+
const originalBranch = await getCurrentBranch(projectDir);
|
|
368
|
+
const hadStash = await stashChanges(projectDir);
|
|
369
|
+
try {
|
|
370
|
+
const outputDir = options.output || join2(projectDir, ".depwire", "temporal");
|
|
371
|
+
const commits = await getCommitLog(projectDir);
|
|
372
|
+
if (commits.length === 0) {
|
|
373
|
+
throw new Error("No commits found in repository");
|
|
374
|
+
}
|
|
375
|
+
console.log(`Found ${commits.length} commits`);
|
|
376
|
+
const sampledCommits = sampleCommits(
|
|
377
|
+
commits,
|
|
378
|
+
options.commits,
|
|
379
|
+
options.strategy
|
|
380
|
+
);
|
|
381
|
+
console.log(
|
|
382
|
+
`Sampled ${sampledCommits.length} commits using ${options.strategy} strategy`
|
|
383
|
+
);
|
|
384
|
+
const snapshots = [];
|
|
385
|
+
for (let i = 0; i < sampledCommits.length; i++) {
|
|
386
|
+
const commit = sampledCommits[i];
|
|
387
|
+
const progress = `[${i + 1}/${sampledCommits.length}]`;
|
|
388
|
+
const existingSnapshot = loadSnapshot(commit.hash, outputDir);
|
|
389
|
+
if (existingSnapshot) {
|
|
390
|
+
if (options.verbose) {
|
|
391
|
+
console.log(
|
|
392
|
+
`${progress} Using cached snapshot for ${commit.hash.substring(0, 8)} - ${commit.message}`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
snapshots.push(existingSnapshot);
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (options.verbose) {
|
|
399
|
+
console.log(
|
|
400
|
+
`${progress} Parsing commit ${commit.hash.substring(0, 8)} - ${commit.message}`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
await checkoutCommit(projectDir, commit.hash);
|
|
404
|
+
const parsedFiles = await parseProject(projectDir);
|
|
405
|
+
const graph = buildGraph(parsedFiles);
|
|
406
|
+
const projectGraph = exportToJSON(graph, projectDir);
|
|
407
|
+
const snapshot = createSnapshot(
|
|
408
|
+
projectGraph,
|
|
409
|
+
commit.hash,
|
|
410
|
+
commit.date,
|
|
411
|
+
commit.message,
|
|
412
|
+
commit.author
|
|
413
|
+
);
|
|
414
|
+
saveSnapshot(snapshot, outputDir);
|
|
415
|
+
snapshots.push(snapshot);
|
|
416
|
+
}
|
|
417
|
+
await restoreOriginal(projectDir, originalBranch);
|
|
418
|
+
if (hadStash) {
|
|
419
|
+
await popStash(projectDir);
|
|
420
|
+
}
|
|
421
|
+
console.log(`\u2713 Created ${snapshots.length} snapshots`);
|
|
422
|
+
if (options.stats) {
|
|
423
|
+
printStats(snapshots);
|
|
424
|
+
}
|
|
425
|
+
console.log("\n\u{1F680} Starting temporal visualization server...");
|
|
426
|
+
await startTemporalServer(snapshots, projectDir, options.port);
|
|
427
|
+
} catch (error) {
|
|
428
|
+
await restoreOriginal(projectDir, originalBranch);
|
|
429
|
+
if (hadStash) {
|
|
430
|
+
await popStash(projectDir);
|
|
431
|
+
}
|
|
432
|
+
throw error;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
function printStats(snapshots) {
|
|
436
|
+
console.log("\n\u{1F4CA} Temporal Analysis Statistics:");
|
|
437
|
+
const first = snapshots[0];
|
|
438
|
+
const last = snapshots[snapshots.length - 1];
|
|
439
|
+
console.log(
|
|
440
|
+
`
|
|
441
|
+
Time Range: ${new Date(first.commitDate).toLocaleDateString()} \u2192 ${new Date(last.commitDate).toLocaleDateString()}`
|
|
442
|
+
);
|
|
443
|
+
console.log(`
|
|
444
|
+
Growth:`);
|
|
445
|
+
console.log(
|
|
446
|
+
` Files: ${first.stats.totalFiles} \u2192 ${last.stats.totalFiles} (${last.stats.totalFiles >= first.stats.totalFiles ? "+" : ""}${last.stats.totalFiles - first.stats.totalFiles})`
|
|
447
|
+
);
|
|
448
|
+
console.log(
|
|
449
|
+
` Symbols: ${first.stats.totalSymbols} \u2192 ${last.stats.totalSymbols} (${last.stats.totalSymbols >= first.stats.totalSymbols ? "+" : ""}${last.stats.totalSymbols - first.stats.totalSymbols})`
|
|
450
|
+
);
|
|
451
|
+
console.log(
|
|
452
|
+
` Edges: ${first.stats.totalEdges} \u2192 ${last.stats.totalEdges} (${last.stats.totalEdges >= first.stats.totalEdges ? "+" : ""}${last.stats.totalEdges - first.stats.totalEdges})`
|
|
453
|
+
);
|
|
454
|
+
let maxGrowth = { index: 0, files: 0 };
|
|
455
|
+
for (let i = 1; i < snapshots.length; i++) {
|
|
456
|
+
const growth = snapshots[i].stats.totalFiles - snapshots[i - 1].stats.totalFiles;
|
|
457
|
+
if (growth > maxGrowth.files) {
|
|
458
|
+
maxGrowth = { index: i, files: growth };
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (maxGrowth.files > 0) {
|
|
462
|
+
const growthCommit = snapshots[maxGrowth.index];
|
|
463
|
+
console.log(`
|
|
464
|
+
Biggest Growth Period:`);
|
|
465
|
+
console.log(
|
|
466
|
+
` +${maxGrowth.files} files at ${new Date(growthCommit.commitDate).toLocaleDateString()}`
|
|
467
|
+
);
|
|
468
|
+
console.log(` ${growthCommit.commitMessage}`);
|
|
469
|
+
}
|
|
470
|
+
const trend = last.stats.totalFiles > first.stats.totalFiles ? "Growing" : last.stats.totalFiles < first.stats.totalFiles ? "Shrinking" : "Stable";
|
|
471
|
+
console.log(`
|
|
472
|
+
Overall Trend: ${trend}`);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// src/index.ts
|
|
476
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
477
|
+
var __dirname2 = dirname2(__filename2);
|
|
478
|
+
var packageJsonPath = join3(__dirname2, "../package.json");
|
|
479
|
+
var packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
|
|
213
480
|
var program = new Command();
|
|
214
481
|
program.name("depwire").description("Code cross-reference graph builder for TypeScript projects").version(packageJson.version);
|
|
215
|
-
program.command("parse").description("Parse a TypeScript project and build dependency graph").argument("
|
|
482
|
+
program.command("parse").description("Parse a TypeScript project and build dependency graph").argument("[directory]", "Project directory to parse (defaults to current directory or auto-detected project root)").option("-o, --output <path>", "Output JSON file path", "depwire-output.json").option("--pretty", "Pretty-print JSON output").option("--stats", "Print summary statistics").option("--exclude <patterns...>", 'Glob patterns to exclude (e.g., "**/*.test.*" "dist/**")').option("--verbose", "Show detailed parsing progress").action(async (directory, options) => {
|
|
216
483
|
const startTime = Date.now();
|
|
217
484
|
try {
|
|
218
|
-
const projectRoot = resolve(directory);
|
|
485
|
+
const projectRoot = directory ? resolve(directory) : findProjectRoot();
|
|
219
486
|
console.log(`Parsing project: ${projectRoot}`);
|
|
220
487
|
const parsedFiles = await parseProject(projectRoot, {
|
|
221
488
|
exclude: options.exclude,
|
|
@@ -258,7 +525,7 @@ program.command("query").description("Query impact analysis for a symbol").argum
|
|
|
258
525
|
let graph;
|
|
259
526
|
if (existsSync(cacheFile)) {
|
|
260
527
|
console.log("Loading from cache...");
|
|
261
|
-
const json = JSON.parse(
|
|
528
|
+
const json = JSON.parse(readFileSync2(cacheFile, "utf-8"));
|
|
262
529
|
graph = importFromJSON(json);
|
|
263
530
|
} else {
|
|
264
531
|
console.log("Parsing project...");
|
|
@@ -299,9 +566,9 @@ Total Transitive Dependents: ${impact.transitiveDependents.length}`);
|
|
|
299
566
|
process.exit(1);
|
|
300
567
|
}
|
|
301
568
|
});
|
|
302
|
-
program.command("viz").description("Launch interactive arc diagram visualization").argument("
|
|
569
|
+
program.command("viz").description("Launch interactive arc diagram visualization").argument("[directory]", "Project directory to visualize (defaults to current directory or auto-detected project root)").option("-p, --port <number>", "Server port", "3333").option("--no-open", "Don't auto-open browser").option("--exclude <patterns...>", 'Glob patterns to exclude (e.g., "**/*.test.*" "dist/**")').option("--verbose", "Show detailed parsing progress").action(async (directory, options) => {
|
|
303
570
|
try {
|
|
304
|
-
const projectRoot = resolve(directory);
|
|
571
|
+
const projectRoot = directory ? resolve(directory) : findProjectRoot();
|
|
305
572
|
console.log(`Parsing project: ${projectRoot}`);
|
|
306
573
|
const parsedFiles = await parseProject(projectRoot, {
|
|
307
574
|
exclude: options.exclude,
|
|
@@ -321,25 +588,50 @@ program.command("viz").description("Launch interactive arc diagram visualization
|
|
|
321
588
|
process.exit(1);
|
|
322
589
|
}
|
|
323
590
|
});
|
|
324
|
-
program.command("
|
|
591
|
+
program.command("temporal").description("Visualize how the dependency graph evolved over git history").argument("[directory]", "Project directory to analyze (defaults to current directory or auto-detected project root)").option("--commits <number>", "Number of commits to sample", "20").option("--strategy <type>", "Sampling strategy: even, weekly, monthly", "even").option("-p, --port <number>", "Server port", "3334").option("--output <path>", "Save snapshots to custom path (default: .depwire/temporal/)").option("--verbose", "Show progress for each commit being parsed").option("--stats", "Show summary statistics at end").action(async (directory, options) => {
|
|
592
|
+
try {
|
|
593
|
+
const projectRoot = directory ? resolve(directory) : findProjectRoot();
|
|
594
|
+
await runTemporalAnalysis(projectRoot, {
|
|
595
|
+
commits: parseInt(options.commits, 10),
|
|
596
|
+
strategy: options.strategy,
|
|
597
|
+
port: parseInt(options.port, 10),
|
|
598
|
+
output: options.output,
|
|
599
|
+
verbose: options.verbose,
|
|
600
|
+
stats: options.stats
|
|
601
|
+
});
|
|
602
|
+
} catch (err) {
|
|
603
|
+
console.error("Error running temporal analysis:", err);
|
|
604
|
+
process.exit(1);
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
program.command("mcp").description("Start MCP server for AI coding tools").argument("[directory]", "Project directory to analyze (optional - auto-detects project root or use connect_repo tool to connect later)").action(async (directory) => {
|
|
325
608
|
try {
|
|
326
609
|
const state = createEmptyState();
|
|
610
|
+
let projectRootToConnect = null;
|
|
327
611
|
if (directory) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
const
|
|
612
|
+
projectRootToConnect = resolve(directory);
|
|
613
|
+
} else {
|
|
614
|
+
const detectedRoot = findProjectRoot();
|
|
615
|
+
const cwd = process.cwd();
|
|
616
|
+
if (detectedRoot !== cwd || existsSync(join3(cwd, "package.json")) || existsSync(join3(cwd, "tsconfig.json")) || existsSync(join3(cwd, "go.mod")) || existsSync(join3(cwd, "pyproject.toml")) || existsSync(join3(cwd, "setup.py")) || existsSync(join3(cwd, ".git"))) {
|
|
617
|
+
projectRootToConnect = detectedRoot;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
if (projectRootToConnect) {
|
|
621
|
+
console.error(`Parsing project: ${projectRootToConnect}`);
|
|
622
|
+
const parsedFiles = await parseProject(projectRootToConnect);
|
|
331
623
|
console.error(`Parsed ${parsedFiles.length} files`);
|
|
332
624
|
const graph = buildGraph(parsedFiles);
|
|
333
625
|
console.error(`Built graph: ${graph.order} symbols, ${graph.size} edges`);
|
|
334
626
|
state.graph = graph;
|
|
335
|
-
state.projectRoot =
|
|
336
|
-
state.projectName =
|
|
627
|
+
state.projectRoot = projectRootToConnect;
|
|
628
|
+
state.projectName = projectRootToConnect.split("/").pop() || "project";
|
|
337
629
|
console.error("Starting file watcher...");
|
|
338
|
-
state.watcher = watchProject(
|
|
630
|
+
state.watcher = watchProject(projectRootToConnect, {
|
|
339
631
|
onFileChanged: async (filePath) => {
|
|
340
632
|
console.error(`File changed: ${filePath}`);
|
|
341
633
|
try {
|
|
342
|
-
await updateFileInGraph(state.graph,
|
|
634
|
+
await updateFileInGraph(state.graph, projectRootToConnect, filePath);
|
|
343
635
|
console.error(`Graph updated for ${filePath}`);
|
|
344
636
|
} catch (error) {
|
|
345
637
|
console.error(`Failed to update graph: ${error}`);
|
|
@@ -348,7 +640,7 @@ program.command("mcp").description("Start MCP server for AI coding tools").argum
|
|
|
348
640
|
onFileAdded: async (filePath) => {
|
|
349
641
|
console.error(`File added: ${filePath}`);
|
|
350
642
|
try {
|
|
351
|
-
await updateFileInGraph(state.graph,
|
|
643
|
+
await updateFileInGraph(state.graph, projectRootToConnect, filePath);
|
|
352
644
|
console.error(`Graph updated for ${filePath}`);
|
|
353
645
|
} catch (error) {
|
|
354
646
|
console.error(`Failed to update graph: ${error}`);
|
|
@@ -374,11 +666,11 @@ program.command("mcp").description("Start MCP server for AI coding tools").argum
|
|
|
374
666
|
process.exit(1);
|
|
375
667
|
}
|
|
376
668
|
});
|
|
377
|
-
program.command("docs").description("Generate comprehensive codebase documentation").argument("
|
|
669
|
+
program.command("docs").description("Generate comprehensive codebase documentation").argument("[directory]", "Project directory to document (defaults to current directory or auto-detected project root)").option("-o, --output <path>", "Output directory (default: .depwire/ inside project)").option("--format <type>", "Output format: markdown | json", "markdown").option("--gitignore", "Add .depwire/ to .gitignore automatically").option("--no-gitignore", "Don't modify .gitignore").option("--include <docs>", "Comma-separated list of docs to generate (default: all)", "all").option("--update", "Regenerate existing docs").option("--only <docs>", "Used with --update, regenerate only specific docs").option("--verbose", "Show generation progress").option("--stats", "Show generation statistics at the end").option("--exclude <patterns...>", 'Glob patterns to exclude (e.g., "**/*.test.*" "dist/**")').action(async (directory, options) => {
|
|
378
670
|
const startTime = Date.now();
|
|
379
671
|
try {
|
|
380
|
-
const projectRoot = resolve(directory);
|
|
381
|
-
const outputDir = options.output ? resolve(options.output) :
|
|
672
|
+
const projectRoot = directory ? resolve(directory) : findProjectRoot();
|
|
673
|
+
const outputDir = options.output ? resolve(options.output) : join3(projectRoot, ".depwire");
|
|
382
674
|
const includeList = options.include.split(",").map((s) => s.trim());
|
|
383
675
|
const onlyList = options.only ? options.only.split(",").map((s) => s.trim()) : void 0;
|
|
384
676
|
if (options.gitignore === void 0 && !existsSyncNode(outputDir)) {
|
|
@@ -449,7 +741,7 @@ async function promptGitignore() {
|
|
|
449
741
|
});
|
|
450
742
|
}
|
|
451
743
|
function addToGitignore(projectRoot, pattern) {
|
|
452
|
-
const gitignorePath =
|
|
744
|
+
const gitignorePath = join3(projectRoot, ".gitignore");
|
|
453
745
|
try {
|
|
454
746
|
let content = "";
|
|
455
747
|
if (existsSyncNode(gitignorePath)) {
|
|
@@ -471,9 +763,9 @@ ${pattern}
|
|
|
471
763
|
console.error(`Warning: Failed to update .gitignore: ${err}`);
|
|
472
764
|
}
|
|
473
765
|
}
|
|
474
|
-
program.command("health
|
|
766
|
+
program.command("health").description("Analyze dependency architecture health (0-100 score)").argument("[directory]", "Project directory to analyze (defaults to current directory or auto-detected project root)").option("--json", "Output as JSON").option("--verbose", "Show detailed breakdown").action(async (directory, options) => {
|
|
475
767
|
try {
|
|
476
|
-
const projectRoot = resolve(
|
|
768
|
+
const projectRoot = directory ? resolve(directory) : findProjectRoot();
|
|
477
769
|
const startTime = Date.now();
|
|
478
770
|
const parsedFiles = await parseProject(projectRoot);
|
|
479
771
|
const graph = buildGraph(parsedFiles);
|