depwire-cli 0.9.20 → 0.9.21
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 +34 -17
- package/dist/{chunk-H6Q2OEGP.js → chunk-QHVWDUSX.js} +382 -1921
- package/dist/chunk-XBCQPU63.js +2002 -0
- package/dist/index.js +20 -356
- package/dist/mcpb-entry.js +5 -3
- package/dist/sdk.d.ts +237 -0
- package/dist/sdk.js +32 -0
- package/package.json +8 -3
package/dist/index.js
CHANGED
|
@@ -1,43 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
analyzeDeadCode,
|
|
4
|
-
buildGraph,
|
|
5
|
-
calculateCircularDepsScore,
|
|
6
|
-
calculateCohesionScore,
|
|
7
|
-
calculateCouplingScore,
|
|
8
|
-
calculateDepthScore,
|
|
9
|
-
calculateGodFilesScore,
|
|
10
|
-
calculateHealthScore,
|
|
11
|
-
calculateOrphansScore,
|
|
12
3
|
checkoutCommit,
|
|
13
4
|
createEmptyState,
|
|
14
5
|
createSnapshot,
|
|
15
|
-
findProjectRoot,
|
|
16
|
-
generateDocs,
|
|
17
|
-
getArchitectureSummary,
|
|
18
6
|
getCommitLog,
|
|
19
7
|
getCurrentBranch,
|
|
20
|
-
getHealthTrend,
|
|
21
|
-
getImpact,
|
|
22
8
|
isGitRepo,
|
|
23
9
|
loadSnapshot,
|
|
24
|
-
parseProject,
|
|
25
10
|
popStash,
|
|
26
11
|
prepareVizData,
|
|
27
12
|
restoreOriginal,
|
|
28
13
|
sampleCommits,
|
|
29
14
|
saveSnapshot,
|
|
30
|
-
searchSymbols,
|
|
31
15
|
startMcpServer,
|
|
32
16
|
startVizServer,
|
|
33
17
|
stashChanges,
|
|
34
18
|
updateFileInGraph,
|
|
35
19
|
watchProject
|
|
36
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-XBCQPU63.js";
|
|
21
|
+
import {
|
|
22
|
+
SimulationEngine,
|
|
23
|
+
analyzeDeadCode,
|
|
24
|
+
buildGraph,
|
|
25
|
+
calculateHealthScore,
|
|
26
|
+
findProjectRoot,
|
|
27
|
+
generateDocs,
|
|
28
|
+
getArchitectureSummary,
|
|
29
|
+
getHealthTrend,
|
|
30
|
+
getImpact,
|
|
31
|
+
parseProject,
|
|
32
|
+
searchSymbols
|
|
33
|
+
} from "./chunk-QHVWDUSX.js";
|
|
37
34
|
|
|
38
35
|
// src/index.ts
|
|
39
36
|
import { Command } from "commander";
|
|
40
|
-
import { resolve as resolve2, dirname as
|
|
37
|
+
import { resolve as resolve2, dirname as dirname2, join as join3 } from "path";
|
|
41
38
|
import { writeFileSync, readFileSync as readFileSync2, existsSync } from "fs";
|
|
42
39
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
43
40
|
|
|
@@ -505,339 +502,6 @@ async function trackCommand(command, version = "unknown") {
|
|
|
505
502
|
// src/commands/whatif.ts
|
|
506
503
|
import { resolve } from "path";
|
|
507
504
|
import chalk from "chalk";
|
|
508
|
-
|
|
509
|
-
// src/simulation/engine.ts
|
|
510
|
-
import { dirname as dirname2, join as join3 } from "path";
|
|
511
|
-
function normalizePath(p) {
|
|
512
|
-
return p.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
513
|
-
}
|
|
514
|
-
function fileMatch(nodeFilePath, target) {
|
|
515
|
-
const a = normalizePath(nodeFilePath);
|
|
516
|
-
const b = normalizePath(target);
|
|
517
|
-
return a === b || a.endsWith("/" + b) || b.endsWith("/" + a);
|
|
518
|
-
}
|
|
519
|
-
var SimulationEngine = class {
|
|
520
|
-
original;
|
|
521
|
-
constructor(graph) {
|
|
522
|
-
this.original = graph;
|
|
523
|
-
}
|
|
524
|
-
simulate(action) {
|
|
525
|
-
const clone = this.original.copy();
|
|
526
|
-
const brokenImports = [];
|
|
527
|
-
switch (action.type) {
|
|
528
|
-
case "move":
|
|
529
|
-
this.applyMove(clone, action.target, action.destination, brokenImports);
|
|
530
|
-
break;
|
|
531
|
-
case "delete":
|
|
532
|
-
this.applyDelete(clone, action.target, brokenImports);
|
|
533
|
-
break;
|
|
534
|
-
case "rename":
|
|
535
|
-
this.applyRename(clone, action.target, action.newName, brokenImports);
|
|
536
|
-
break;
|
|
537
|
-
case "split":
|
|
538
|
-
this.applySplit(clone, action.target, action.newFile, action.symbols, brokenImports);
|
|
539
|
-
break;
|
|
540
|
-
case "merge":
|
|
541
|
-
this.applyMerge(clone, action.target, action.source, brokenImports);
|
|
542
|
-
break;
|
|
543
|
-
}
|
|
544
|
-
const diff = this.computeDiff(this.original, clone, brokenImports);
|
|
545
|
-
const beforeHealth = this.computeHealthScore(this.original);
|
|
546
|
-
const afterHealth = this.computeHealthScore(clone);
|
|
547
|
-
const dimensionChanges = beforeHealth.dimensions.map((bd, i) => {
|
|
548
|
-
const ad = afterHealth.dimensions[i];
|
|
549
|
-
return {
|
|
550
|
-
name: bd.name,
|
|
551
|
-
before: bd.score,
|
|
552
|
-
after: ad ? ad.score : bd.score,
|
|
553
|
-
delta: (ad ? ad.score : bd.score) - bd.score
|
|
554
|
-
};
|
|
555
|
-
});
|
|
556
|
-
const healthDelta = {
|
|
557
|
-
before: beforeHealth.score,
|
|
558
|
-
after: afterHealth.score,
|
|
559
|
-
delta: afterHealth.score - beforeHealth.score,
|
|
560
|
-
improved: afterHealth.score > beforeHealth.score,
|
|
561
|
-
dimensionChanges
|
|
562
|
-
};
|
|
563
|
-
return {
|
|
564
|
-
action,
|
|
565
|
-
originalGraph: {
|
|
566
|
-
nodeCount: this.original.order,
|
|
567
|
-
edgeCount: this.original.size,
|
|
568
|
-
healthScore: beforeHealth.score
|
|
569
|
-
},
|
|
570
|
-
simulatedGraph: {
|
|
571
|
-
nodeCount: clone.order,
|
|
572
|
-
edgeCount: clone.size,
|
|
573
|
-
healthScore: afterHealth.score
|
|
574
|
-
},
|
|
575
|
-
diff,
|
|
576
|
-
healthDelta
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
// ── Action implementations ─────────────────────────────────────
|
|
580
|
-
applyMove(clone, target, destination, brokenImports) {
|
|
581
|
-
const normalizedTarget = normalizePath(target);
|
|
582
|
-
const normalizedDest = normalizePath(destination);
|
|
583
|
-
const nodesToMove = clone.filterNodes(
|
|
584
|
-
(_node, attrs) => fileMatch(attrs.filePath, target)
|
|
585
|
-
);
|
|
586
|
-
if (nodesToMove.length === 0) return;
|
|
587
|
-
for (const oldId of nodesToMove) {
|
|
588
|
-
const attrs = clone.getNodeAttributes(oldId);
|
|
589
|
-
const symbolName = oldId.includes("::") ? oldId.split("::").slice(1).join("::") : attrs.name;
|
|
590
|
-
const newId = `${normalizedDest}::${symbolName}`;
|
|
591
|
-
clone.forEachInEdge(oldId, (edge, edgeAttrs, source) => {
|
|
592
|
-
const sourceAttrs = clone.getNodeAttributes(source);
|
|
593
|
-
if (!fileMatch(sourceAttrs.filePath, target)) {
|
|
594
|
-
brokenImports.push({
|
|
595
|
-
file: sourceAttrs.filePath,
|
|
596
|
-
importedSymbol: attrs.name,
|
|
597
|
-
reason: `imports ${attrs.name} from ${target} (path would break)`
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
});
|
|
601
|
-
if (!clone.hasNode(newId)) {
|
|
602
|
-
clone.addNode(newId, { ...attrs, filePath: normalizedDest });
|
|
603
|
-
}
|
|
604
|
-
clone.forEachInEdge(oldId, (edge, edgeAttrs, source) => {
|
|
605
|
-
const newSource = nodesToMove.includes(source) ? `${normalizedDest}::${source.includes("::") ? source.split("::").slice(1).join("::") : clone.getNodeAttributes(source).name}` : source;
|
|
606
|
-
if (clone.hasNode(newSource) && clone.hasNode(newId)) {
|
|
607
|
-
clone.mergeEdge(newSource, newId, edgeAttrs);
|
|
608
|
-
}
|
|
609
|
-
});
|
|
610
|
-
clone.forEachOutEdge(oldId, (edge, edgeAttrs, _source, outTarget) => {
|
|
611
|
-
const newTarget = nodesToMove.includes(outTarget) ? `${normalizedDest}::${outTarget.includes("::") ? outTarget.split("::").slice(1).join("::") : clone.getNodeAttributes(outTarget).name}` : outTarget;
|
|
612
|
-
if (clone.hasNode(newId) && clone.hasNode(newTarget)) {
|
|
613
|
-
clone.mergeEdge(newId, newTarget, edgeAttrs);
|
|
614
|
-
}
|
|
615
|
-
});
|
|
616
|
-
clone.dropNode(oldId);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
applyDelete(clone, target, brokenImports) {
|
|
620
|
-
const nodesToDelete = clone.filterNodes(
|
|
621
|
-
(_node, attrs) => fileMatch(attrs.filePath, target)
|
|
622
|
-
);
|
|
623
|
-
for (const nodeId of nodesToDelete) {
|
|
624
|
-
const attrs = clone.getNodeAttributes(nodeId);
|
|
625
|
-
clone.forEachInEdge(nodeId, (_edge, _edgeAttrs, source) => {
|
|
626
|
-
const sourceAttrs = clone.getNodeAttributes(source);
|
|
627
|
-
if (!fileMatch(sourceAttrs.filePath, target)) {
|
|
628
|
-
brokenImports.push({
|
|
629
|
-
file: sourceAttrs.filePath,
|
|
630
|
-
importedSymbol: attrs.name,
|
|
631
|
-
reason: `imports ${attrs.name} from ${target} (file deleted)`
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
for (const nodeId of nodesToDelete) {
|
|
637
|
-
clone.dropNode(nodeId);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
applyRename(clone, target, newName, brokenImports) {
|
|
641
|
-
const destination = join3(dirname2(target), newName);
|
|
642
|
-
this.applyMove(clone, target, destination, brokenImports);
|
|
643
|
-
}
|
|
644
|
-
applySplit(clone, target, newFile, symbols, brokenImports) {
|
|
645
|
-
const normalizedNewFile = normalizePath(newFile);
|
|
646
|
-
const nodesToSplit = clone.filterNodes((_node, attrs) => {
|
|
647
|
-
return fileMatch(attrs.filePath, target) && symbols.includes(attrs.name);
|
|
648
|
-
});
|
|
649
|
-
if (nodesToSplit.length === 0) return;
|
|
650
|
-
for (const oldId of nodesToSplit) {
|
|
651
|
-
const attrs = clone.getNodeAttributes(oldId);
|
|
652
|
-
const symbolName = oldId.includes("::") ? oldId.split("::").slice(1).join("::") : attrs.name;
|
|
653
|
-
const newId = `${normalizedNewFile}::${symbolName}`;
|
|
654
|
-
clone.forEachInEdge(oldId, (_edge, _edgeAttrs, source) => {
|
|
655
|
-
const sourceAttrs = clone.getNodeAttributes(source);
|
|
656
|
-
if (!fileMatch(sourceAttrs.filePath, target) && !fileMatch(sourceAttrs.filePath, newFile)) {
|
|
657
|
-
brokenImports.push({
|
|
658
|
-
file: sourceAttrs.filePath,
|
|
659
|
-
importedSymbol: attrs.name,
|
|
660
|
-
reason: `imports ${attrs.name} from ${target} (symbol moved to ${newFile})`
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
});
|
|
664
|
-
if (!clone.hasNode(newId)) {
|
|
665
|
-
clone.addNode(newId, { ...attrs, filePath: normalizedNewFile });
|
|
666
|
-
}
|
|
667
|
-
clone.forEachInEdge(oldId, (_edge, edgeAttrs, source) => {
|
|
668
|
-
if (clone.hasNode(source) && clone.hasNode(newId)) {
|
|
669
|
-
clone.mergeEdge(source, newId, edgeAttrs);
|
|
670
|
-
}
|
|
671
|
-
});
|
|
672
|
-
clone.forEachOutEdge(oldId, (_edge, edgeAttrs, _source, outTarget) => {
|
|
673
|
-
if (clone.hasNode(newId) && clone.hasNode(outTarget)) {
|
|
674
|
-
clone.mergeEdge(newId, outTarget, edgeAttrs);
|
|
675
|
-
}
|
|
676
|
-
});
|
|
677
|
-
clone.dropNode(oldId);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
applyMerge(clone, target, source, brokenImports) {
|
|
681
|
-
const normalizedTarget = normalizePath(target);
|
|
682
|
-
const sourceNodes = clone.filterNodes(
|
|
683
|
-
(_node, attrs) => fileMatch(attrs.filePath, source)
|
|
684
|
-
);
|
|
685
|
-
const targetNodes = clone.filterNodes(
|
|
686
|
-
(_node, attrs) => fileMatch(attrs.filePath, target)
|
|
687
|
-
);
|
|
688
|
-
const targetSymbols = new Set(
|
|
689
|
-
targetNodes.map((n) => clone.getNodeAttributes(n).name)
|
|
690
|
-
);
|
|
691
|
-
for (const nodeId of sourceNodes) {
|
|
692
|
-
const name = clone.getNodeAttributes(nodeId).name;
|
|
693
|
-
if (name !== "__file__" && targetSymbols.has(name)) {
|
|
694
|
-
throw new Error(
|
|
695
|
-
`Merge conflict: symbol "${name}" exists in both ${target} and ${source}`
|
|
696
|
-
);
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
for (const oldId of sourceNodes) {
|
|
700
|
-
const attrs = clone.getNodeAttributes(oldId);
|
|
701
|
-
const symbolName = oldId.includes("::") ? oldId.split("::").slice(1).join("::") : attrs.name;
|
|
702
|
-
const newId = `${normalizedTarget}::${symbolName}`;
|
|
703
|
-
clone.forEachInEdge(oldId, (_edge, _edgeAttrs, inSource) => {
|
|
704
|
-
const srcAttrs = clone.getNodeAttributes(inSource);
|
|
705
|
-
if (!fileMatch(srcAttrs.filePath, source) && !fileMatch(srcAttrs.filePath, target)) {
|
|
706
|
-
brokenImports.push({
|
|
707
|
-
file: srcAttrs.filePath,
|
|
708
|
-
importedSymbol: attrs.name,
|
|
709
|
-
reason: `imports ${attrs.name} from ${source} (merged into ${target})`
|
|
710
|
-
});
|
|
711
|
-
}
|
|
712
|
-
});
|
|
713
|
-
if (!clone.hasNode(newId)) {
|
|
714
|
-
clone.addNode(newId, { ...attrs, filePath: normalizedTarget });
|
|
715
|
-
}
|
|
716
|
-
clone.forEachInEdge(oldId, (_edge, edgeAttrs, inSource) => {
|
|
717
|
-
const resolvedSource = sourceNodes.includes(inSource) ? `${normalizedTarget}::${inSource.includes("::") ? inSource.split("::").slice(1).join("::") : clone.getNodeAttributes(inSource).name}` : inSource;
|
|
718
|
-
if (clone.hasNode(resolvedSource) && clone.hasNode(newId)) {
|
|
719
|
-
clone.mergeEdge(resolvedSource, newId, edgeAttrs);
|
|
720
|
-
}
|
|
721
|
-
});
|
|
722
|
-
clone.forEachOutEdge(oldId, (_edge, edgeAttrs, _s, outTarget) => {
|
|
723
|
-
const resolvedTarget = sourceNodes.includes(outTarget) ? `${normalizedTarget}::${outTarget.includes("::") ? outTarget.split("::").slice(1).join("::") : clone.getNodeAttributes(outTarget).name}` : outTarget;
|
|
724
|
-
if (clone.hasNode(newId) && clone.hasNode(resolvedTarget)) {
|
|
725
|
-
clone.mergeEdge(newId, resolvedTarget, edgeAttrs);
|
|
726
|
-
}
|
|
727
|
-
});
|
|
728
|
-
clone.dropNode(oldId);
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
// ── Diff computation ───────────────────────────────────────────
|
|
732
|
-
computeDiff(original, simulated, brokenImports) {
|
|
733
|
-
const originalEdges = this.collectEdges(original);
|
|
734
|
-
const simulatedEdges = this.collectEdges(simulated);
|
|
735
|
-
const originalKeys = new Set(originalEdges.map((e) => this.edgeKey(e)));
|
|
736
|
-
const simulatedKeys = new Set(simulatedEdges.map((e) => this.edgeKey(e)));
|
|
737
|
-
const addedEdges = simulatedEdges.filter((e) => !originalKeys.has(this.edgeKey(e)));
|
|
738
|
-
const removedEdges = originalEdges.filter((e) => !simulatedKeys.has(this.edgeKey(e)));
|
|
739
|
-
const affectedNodeSet = /* @__PURE__ */ new Set();
|
|
740
|
-
for (const e of [...addedEdges, ...removedEdges]) {
|
|
741
|
-
affectedNodeSet.add(e.source);
|
|
742
|
-
affectedNodeSet.add(e.target);
|
|
743
|
-
}
|
|
744
|
-
const originalCycles = this.detectCycles(original);
|
|
745
|
-
const simulatedCycles = this.detectCycles(simulated);
|
|
746
|
-
const originalCycleKeys = new Set(originalCycles.map((c) => [...c].sort().join(",")));
|
|
747
|
-
const simulatedCycleKeys = new Set(simulatedCycles.map((c) => [...c].sort().join(",")));
|
|
748
|
-
const circularDepsIntroduced = simulatedCycles.filter(
|
|
749
|
-
(c) => !originalCycleKeys.has([...c].sort().join(","))
|
|
750
|
-
);
|
|
751
|
-
const circularDepsResolved = originalCycles.filter(
|
|
752
|
-
(c) => !simulatedCycleKeys.has([...c].sort().join(","))
|
|
753
|
-
);
|
|
754
|
-
return {
|
|
755
|
-
addedEdges,
|
|
756
|
-
removedEdges,
|
|
757
|
-
affectedNodes: Array.from(affectedNodeSet),
|
|
758
|
-
brokenImports,
|
|
759
|
-
circularDepsIntroduced,
|
|
760
|
-
circularDepsResolved
|
|
761
|
-
};
|
|
762
|
-
}
|
|
763
|
-
collectEdges(graph) {
|
|
764
|
-
const edges = [];
|
|
765
|
-
graph.forEachEdge((_edge, attrs, source, target) => {
|
|
766
|
-
edges.push({ source, target, kind: attrs.kind });
|
|
767
|
-
});
|
|
768
|
-
return edges;
|
|
769
|
-
}
|
|
770
|
-
edgeKey(e) {
|
|
771
|
-
return `${e.source}|${e.target}|${e.kind || ""}`;
|
|
772
|
-
}
|
|
773
|
-
// ── Cycle detection (adapted from src/health/metrics.ts) ───────
|
|
774
|
-
detectCycles(graph) {
|
|
775
|
-
const fileGraph = /* @__PURE__ */ new Map();
|
|
776
|
-
graph.forEachEdge((_edge, _attrs, source, target) => {
|
|
777
|
-
const sourceFile = graph.getNodeAttributes(source).filePath;
|
|
778
|
-
const targetFile = graph.getNodeAttributes(target).filePath;
|
|
779
|
-
if (sourceFile !== targetFile) {
|
|
780
|
-
if (!fileGraph.has(sourceFile)) {
|
|
781
|
-
fileGraph.set(sourceFile, /* @__PURE__ */ new Set());
|
|
782
|
-
}
|
|
783
|
-
fileGraph.get(sourceFile).add(targetFile);
|
|
784
|
-
}
|
|
785
|
-
});
|
|
786
|
-
const visited = /* @__PURE__ */ new Set();
|
|
787
|
-
const recStack = /* @__PURE__ */ new Set();
|
|
788
|
-
const cycles = [];
|
|
789
|
-
const dfs = (node, path) => {
|
|
790
|
-
if (recStack.has(node)) {
|
|
791
|
-
const cycleStart = path.indexOf(node);
|
|
792
|
-
if (cycleStart >= 0) {
|
|
793
|
-
cycles.push(path.slice(cycleStart));
|
|
794
|
-
}
|
|
795
|
-
return;
|
|
796
|
-
}
|
|
797
|
-
if (visited.has(node)) return;
|
|
798
|
-
visited.add(node);
|
|
799
|
-
recStack.add(node);
|
|
800
|
-
path.push(node);
|
|
801
|
-
const neighbors = fileGraph.get(node);
|
|
802
|
-
if (neighbors) {
|
|
803
|
-
for (const neighbor of neighbors) {
|
|
804
|
-
dfs(neighbor, [...path]);
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
recStack.delete(node);
|
|
808
|
-
};
|
|
809
|
-
for (const node of fileGraph.keys()) {
|
|
810
|
-
if (!visited.has(node)) {
|
|
811
|
-
dfs(node, []);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
const unique = /* @__PURE__ */ new Map();
|
|
815
|
-
for (const cycle of cycles) {
|
|
816
|
-
const key = [...cycle].sort().join(",");
|
|
817
|
-
if (!unique.has(key)) {
|
|
818
|
-
unique.set(key, cycle);
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
return Array.from(unique.values());
|
|
822
|
-
}
|
|
823
|
-
// ── Health score (side-effect free) ────────────────────────────
|
|
824
|
-
computeHealthScore(graph) {
|
|
825
|
-
const dimensions = [
|
|
826
|
-
calculateCouplingScore(graph),
|
|
827
|
-
calculateCohesionScore(graph),
|
|
828
|
-
calculateCircularDepsScore(graph),
|
|
829
|
-
calculateGodFilesScore(graph),
|
|
830
|
-
calculateOrphansScore(graph),
|
|
831
|
-
calculateDepthScore(graph)
|
|
832
|
-
];
|
|
833
|
-
const score = Math.round(
|
|
834
|
-
dimensions.reduce((sum, dim) => sum + dim.score * dim.weight, 0)
|
|
835
|
-
);
|
|
836
|
-
return { score, dimensions };
|
|
837
|
-
}
|
|
838
|
-
};
|
|
839
|
-
|
|
840
|
-
// src/commands/whatif.ts
|
|
841
505
|
async function whatif(dir, options) {
|
|
842
506
|
if (!options.simulate) {
|
|
843
507
|
console.log("Usage: depwire whatif [dir] --simulate <action> --target <file> [options]");
|
|
@@ -969,8 +633,8 @@ function formatAction(action) {
|
|
|
969
633
|
|
|
970
634
|
// src/index.ts
|
|
971
635
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
972
|
-
var __dirname2 =
|
|
973
|
-
var packageJsonPath =
|
|
636
|
+
var __dirname2 = dirname2(__filename2);
|
|
637
|
+
var packageJsonPath = join3(__dirname2, "../package.json");
|
|
974
638
|
var packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
|
|
975
639
|
var program = new Command();
|
|
976
640
|
program.name("depwire").description("Code cross-reference graph builder for TypeScript projects").version(packageJson.version);
|
|
@@ -1113,7 +777,7 @@ program.command("mcp").description("Start MCP server for AI coding tools").argum
|
|
|
1113
777
|
} else {
|
|
1114
778
|
const detectedRoot = findProjectRoot();
|
|
1115
779
|
const cwd = process.cwd();
|
|
1116
|
-
if (detectedRoot !== cwd || existsSync(
|
|
780
|
+
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"))) {
|
|
1117
781
|
projectRootToConnect = detectedRoot;
|
|
1118
782
|
}
|
|
1119
783
|
}
|
|
@@ -1171,7 +835,7 @@ program.command("docs").description("Generate comprehensive codebase documentati
|
|
|
1171
835
|
const startTime = Date.now();
|
|
1172
836
|
try {
|
|
1173
837
|
const projectRoot = directory ? resolve2(directory) : findProjectRoot();
|
|
1174
|
-
const outputDir = options.output ? resolve2(options.output) :
|
|
838
|
+
const outputDir = options.output ? resolve2(options.output) : join3(projectRoot, ".depwire");
|
|
1175
839
|
const includeList = options.include.split(",").map((s) => s.trim());
|
|
1176
840
|
const onlyList = options.only ? options.only.split(",").map((s) => s.trim()) : void 0;
|
|
1177
841
|
if (options.gitignore === void 0 && !existsSyncNode(outputDir)) {
|
|
@@ -1242,7 +906,7 @@ async function promptGitignore() {
|
|
|
1242
906
|
});
|
|
1243
907
|
}
|
|
1244
908
|
function addToGitignore(projectRoot, pattern) {
|
|
1245
|
-
const gitignorePath =
|
|
909
|
+
const gitignorePath = join3(projectRoot, ".gitignore");
|
|
1246
910
|
try {
|
|
1247
911
|
let content = "";
|
|
1248
912
|
if (existsSyncNode(gitignorePath)) {
|
package/dist/mcpb-entry.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
buildGraph,
|
|
4
3
|
createEmptyState,
|
|
5
|
-
parseProject,
|
|
6
4
|
startMcpServer,
|
|
7
5
|
updateFileInGraph,
|
|
8
6
|
watchProject
|
|
9
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-XBCQPU63.js";
|
|
8
|
+
import {
|
|
9
|
+
buildGraph,
|
|
10
|
+
parseProject
|
|
11
|
+
} from "./chunk-QHVWDUSX.js";
|
|
10
12
|
|
|
11
13
|
// src/mcpb-entry.ts
|
|
12
14
|
import { resolve } from "path";
|
package/dist/sdk.d.ts
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { DirectedGraph } from 'graphology';
|
|
2
|
+
|
|
3
|
+
type SymbolKind = 'function' | 'class' | 'variable' | 'constant' | 'type_alias' | 'interface' | 'enum' | 'import' | 'export' | 'method' | 'property' | 'decorator' | 'module';
|
|
4
|
+
interface SymbolNode {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
kind: SymbolKind;
|
|
8
|
+
filePath: string;
|
|
9
|
+
startLine: number;
|
|
10
|
+
endLine: number;
|
|
11
|
+
exported: boolean;
|
|
12
|
+
scope?: string;
|
|
13
|
+
}
|
|
14
|
+
type EdgeKind = 'imports' | 'calls' | 'extends' | 'implements' | 'inherits' | 'decorates' | 'references' | 'type_references';
|
|
15
|
+
interface SymbolEdge {
|
|
16
|
+
source: string;
|
|
17
|
+
target: string;
|
|
18
|
+
kind: EdgeKind;
|
|
19
|
+
filePath: string;
|
|
20
|
+
line: number;
|
|
21
|
+
}
|
|
22
|
+
interface ParsedFile {
|
|
23
|
+
filePath: string;
|
|
24
|
+
symbols: SymbolNode[];
|
|
25
|
+
edges: SymbolEdge[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* SECURITY: All parser operations are READ-ONLY.
|
|
30
|
+
* Depwire never writes to, modifies, or deletes any file in the user's project.
|
|
31
|
+
* The only file system writes are to os.tmpdir() for cloned repos.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
declare function parseProject(projectRoot: string, options?: {
|
|
35
|
+
exclude?: string[];
|
|
36
|
+
verbose?: boolean;
|
|
37
|
+
}): Promise<ParsedFile[]>;
|
|
38
|
+
|
|
39
|
+
declare function buildGraph(parsedFiles: ParsedFile[]): DirectedGraph;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Health Score Type Definitions
|
|
43
|
+
*/
|
|
44
|
+
interface HealthDimension {
|
|
45
|
+
name: string;
|
|
46
|
+
score: number;
|
|
47
|
+
weight: number;
|
|
48
|
+
grade: string;
|
|
49
|
+
details: string;
|
|
50
|
+
metrics: Record<string, number | string>;
|
|
51
|
+
}
|
|
52
|
+
interface HealthReport {
|
|
53
|
+
overall: number;
|
|
54
|
+
grade: string;
|
|
55
|
+
dimensions: HealthDimension[];
|
|
56
|
+
summary: string;
|
|
57
|
+
recommendations: string[];
|
|
58
|
+
projectStats: {
|
|
59
|
+
files: number;
|
|
60
|
+
symbols: number;
|
|
61
|
+
edges: number;
|
|
62
|
+
languages: Record<string, number>;
|
|
63
|
+
};
|
|
64
|
+
timestamp: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Calculate the overall health score for a project
|
|
69
|
+
*/
|
|
70
|
+
declare function calculateHealthScore(graph: DirectedGraph, projectRoot: string): HealthReport;
|
|
71
|
+
|
|
72
|
+
type ConfidenceLevel = "high" | "medium" | "low";
|
|
73
|
+
interface DeadSymbol {
|
|
74
|
+
name: string;
|
|
75
|
+
kind: string;
|
|
76
|
+
file: string;
|
|
77
|
+
line: number;
|
|
78
|
+
exported: boolean;
|
|
79
|
+
dependents: number;
|
|
80
|
+
confidence: ConfidenceLevel;
|
|
81
|
+
reason: string;
|
|
82
|
+
}
|
|
83
|
+
interface DeadCodeReport {
|
|
84
|
+
totalSymbols: number;
|
|
85
|
+
deadSymbols: number;
|
|
86
|
+
deadPercentage: number;
|
|
87
|
+
byConfidence: {
|
|
88
|
+
high: number;
|
|
89
|
+
medium: number;
|
|
90
|
+
low: number;
|
|
91
|
+
};
|
|
92
|
+
symbols: DeadSymbol[];
|
|
93
|
+
}
|
|
94
|
+
interface DeadCodeOptions {
|
|
95
|
+
confidence: ConfidenceLevel;
|
|
96
|
+
includeTests: boolean;
|
|
97
|
+
verbose: boolean;
|
|
98
|
+
stats: boolean;
|
|
99
|
+
json: boolean;
|
|
100
|
+
debug: boolean;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
declare function analyzeDeadCode(graph: DirectedGraph, projectRoot: string, options?: Partial<DeadCodeOptions>): DeadCodeReport;
|
|
104
|
+
|
|
105
|
+
interface GeneratorOptions {
|
|
106
|
+
outputDir: string;
|
|
107
|
+
format: 'markdown' | 'json';
|
|
108
|
+
include: string[];
|
|
109
|
+
update: boolean;
|
|
110
|
+
only?: string[];
|
|
111
|
+
verbose: boolean;
|
|
112
|
+
stats: boolean;
|
|
113
|
+
}
|
|
114
|
+
interface GenerationResult {
|
|
115
|
+
success: boolean;
|
|
116
|
+
generated: string[];
|
|
117
|
+
errors: string[];
|
|
118
|
+
stats?: {
|
|
119
|
+
totalTime: number;
|
|
120
|
+
filesGenerated: number;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Main documentation generator
|
|
125
|
+
*/
|
|
126
|
+
declare function generateDocs(graph: DirectedGraph, projectRoot: string, version: string, parseTime: number, options: GeneratorOptions): Promise<GenerationResult>;
|
|
127
|
+
|
|
128
|
+
declare function getImpact(graph: DirectedGraph, symbolId: string): {
|
|
129
|
+
directDependents: SymbolNode[];
|
|
130
|
+
transitiveDependents: SymbolNode[];
|
|
131
|
+
affectedFiles: string[];
|
|
132
|
+
};
|
|
133
|
+
declare function searchSymbols(graph: DirectedGraph, query: string): SymbolNode[];
|
|
134
|
+
declare function getArchitectureSummary(graph: DirectedGraph): {
|
|
135
|
+
fileCount: number;
|
|
136
|
+
symbolCount: number;
|
|
137
|
+
edgeCount: number;
|
|
138
|
+
mostConnectedFiles: {
|
|
139
|
+
filePath: string;
|
|
140
|
+
connections: number;
|
|
141
|
+
}[];
|
|
142
|
+
orphanFiles: string[];
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
type SimulationAction = {
|
|
146
|
+
type: 'move';
|
|
147
|
+
target: string;
|
|
148
|
+
destination: string;
|
|
149
|
+
} | {
|
|
150
|
+
type: 'delete';
|
|
151
|
+
target: string;
|
|
152
|
+
} | {
|
|
153
|
+
type: 'rename';
|
|
154
|
+
target: string;
|
|
155
|
+
newName: string;
|
|
156
|
+
} | {
|
|
157
|
+
type: 'split';
|
|
158
|
+
target: string;
|
|
159
|
+
newFile: string;
|
|
160
|
+
symbols: string[];
|
|
161
|
+
} | {
|
|
162
|
+
type: 'merge';
|
|
163
|
+
target: string;
|
|
164
|
+
source: string;
|
|
165
|
+
};
|
|
166
|
+
interface SimulationResult {
|
|
167
|
+
action: SimulationAction;
|
|
168
|
+
originalGraph: GraphSnapshot;
|
|
169
|
+
simulatedGraph: GraphSnapshot;
|
|
170
|
+
diff: GraphDiff;
|
|
171
|
+
healthDelta: HealthDelta;
|
|
172
|
+
}
|
|
173
|
+
interface GraphSnapshot {
|
|
174
|
+
nodeCount: number;
|
|
175
|
+
edgeCount: number;
|
|
176
|
+
healthScore: number;
|
|
177
|
+
}
|
|
178
|
+
interface GraphDiff {
|
|
179
|
+
addedEdges: EdgeInfo[];
|
|
180
|
+
removedEdges: EdgeInfo[];
|
|
181
|
+
affectedNodes: string[];
|
|
182
|
+
brokenImports: BrokenImport[];
|
|
183
|
+
circularDepsIntroduced: string[][];
|
|
184
|
+
circularDepsResolved: string[][];
|
|
185
|
+
}
|
|
186
|
+
interface HealthDelta {
|
|
187
|
+
before: number;
|
|
188
|
+
after: number;
|
|
189
|
+
delta: number;
|
|
190
|
+
improved: boolean;
|
|
191
|
+
dimensionChanges: DimensionChange[];
|
|
192
|
+
}
|
|
193
|
+
interface DimensionChange {
|
|
194
|
+
name: string;
|
|
195
|
+
before: number;
|
|
196
|
+
after: number;
|
|
197
|
+
delta: number;
|
|
198
|
+
}
|
|
199
|
+
interface BrokenImport {
|
|
200
|
+
file: string;
|
|
201
|
+
importedSymbol: string;
|
|
202
|
+
reason: string;
|
|
203
|
+
}
|
|
204
|
+
interface EdgeInfo {
|
|
205
|
+
source: string;
|
|
206
|
+
target: string;
|
|
207
|
+
kind?: string;
|
|
208
|
+
}
|
|
209
|
+
declare class SimulationEngine {
|
|
210
|
+
private readonly original;
|
|
211
|
+
constructor(graph: DirectedGraph);
|
|
212
|
+
simulate(action: SimulationAction): SimulationResult;
|
|
213
|
+
private applyMove;
|
|
214
|
+
private applyDelete;
|
|
215
|
+
private applyRename;
|
|
216
|
+
private applySplit;
|
|
217
|
+
private applyMerge;
|
|
218
|
+
private computeDiff;
|
|
219
|
+
private collectEdges;
|
|
220
|
+
private edgeKey;
|
|
221
|
+
private detectCycles;
|
|
222
|
+
private computeHealthScore;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* depwire-cli SDK — Public API Surface
|
|
227
|
+
*
|
|
228
|
+
* This is the ONLY file the cloud (Railway parser) should import from.
|
|
229
|
+
* Never import from internal paths like depwire-cli/dist/graph/index.js.
|
|
230
|
+
*
|
|
231
|
+
* Rule: if the cloud needs something not exported here, add it here —
|
|
232
|
+
* do not reach into internal paths.
|
|
233
|
+
*/
|
|
234
|
+
/** Current SDK version — matches depwire-cli npm version */
|
|
235
|
+
declare const DepwireSDKVersion: string;
|
|
236
|
+
|
|
237
|
+
export { type BrokenImport, DepwireSDKVersion, type GraphDiff, type HealthDelta, type SimulationAction, SimulationEngine, type SimulationResult, analyzeDeadCode, buildGraph, calculateHealthScore, generateDocs, getArchitectureSummary, getImpact, parseProject, searchSymbols };
|