modviz 0.1.2
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 +261 -0
- package/dist/client/_shell.html +0 -0
- package/dist/client/android-chrome-192x192.png +0 -0
- package/dist/client/android-chrome-512x512.png +0 -0
- package/dist/client/apple-touch-icon.png +0 -0
- package/dist/client/assets/app-Sjrldkrg.css +2 -0
- package/dist/client/assets/button-aOWckyNs.js +1 -0
- package/dist/client/assets/check-C0EQe2S8.js +1 -0
- package/dist/client/assets/chevron-down-DrspihmT.js +1 -0
- package/dist/client/assets/chevron-right-DIJHr8AN.js +1 -0
- package/dist/client/assets/colors-CQoWjU5E.js +1 -0
- package/dist/client/assets/command-kkF7_wdz.js +45 -0
- package/dist/client/assets/compare-K6jVFsiI.js +1 -0
- package/dist/client/assets/compare-TOnoe1EP.js +2 -0
- package/dist/client/assets/configure-DnlSnhtN.js +1 -0
- package/dist/client/assets/explorer-C7NclVKg.js +2 -0
- package/dist/client/assets/explorer-Xu2X6XXF.js +1 -0
- package/dist/client/assets/external-link-B9eNA-li.js +1 -0
- package/dist/client/assets/flamegraph-CRVZSAlj.js +13 -0
- package/dist/client/assets/floating-ui.dom-DLIT5tPE.js +1 -0
- package/dist/client/assets/formatting-CiC0SYI8.js +1 -0
- package/dist/client/assets/graph-6Vr74V1k.js +2 -0
- package/dist/client/assets/graph-CVzypIGU.js +2 -0
- package/dist/client/assets/graph-command-menu-D2MoVT2B.js +4 -0
- package/dist/client/assets/graph-command-menu-VWiiW3qy.css +1 -0
- package/dist/client/assets/hierarchy-C8xxGb_u.js +2 -0
- package/dist/client/assets/hierarchy-iO7d4oSK.js +2 -0
- package/dist/client/assets/import-display-D-jRyyjM.js +5 -0
- package/dist/client/assets/imports-CPggnrs-.js +2 -0
- package/dist/client/assets/imports-CodbPyUJ.js +1 -0
- package/dist/client/assets/index-Dj_rhLdR.js +12 -0
- package/dist/client/assets/input-BCFMF0aR.js +1 -0
- package/dist/client/assets/jsx-runtime-DWSWI4JT.js +1 -0
- package/dist/client/assets/lazyRouteComponent-PTSyFp1J.js +1 -0
- package/dist/client/assets/loading-state-CyC_hrTF.js +1 -0
- package/dist/client/assets/modviz-data-BiRqoDI5.js +1 -0
- package/dist/client/assets/modviz-layout-Do93E-IB.js +1 -0
- package/dist/client/assets/modviz-sigma-Xl8qHaxK.js +312 -0
- package/dist/client/assets/portal-BgAm3V3j.js +1 -0
- package/dist/client/assets/routes-DBtN8hrZ.js +1 -0
- package/dist/client/assets/schemas-B4zfTepZ.js +39 -0
- package/dist/client/assets/search-BYHxNrYn.js +1 -0
- package/dist/client/assets/search-params-BaZRBvGI.js +1 -0
- package/dist/client/assets/setup-view-j1o0TuZz.js +1 -0
- package/dist/client/assets/summary-D703Zh3x.js +1 -0
- package/dist/client/assets/tooltip-B1VDU9HG.js +1 -0
- package/dist/client/assets/trace-B67CM5s2.js +2 -0
- package/dist/client/assets/trace-Bwwdw3AM.js +1 -0
- package/dist/client/assets/treemap-BZf2shzY.js +5 -0
- package/dist/client/assets/treemap-Csroy8Gy.js +2 -0
- package/dist/client/assets/utils-DkkZd0ys.js +1 -0
- package/dist/client/favicon-16x16.png +0 -0
- package/dist/client/favicon-32x32.png +0 -0
- package/dist/client/favicon.ico +0 -0
- package/dist/client/favicon.png +0 -0
- package/dist/client/site.webmanifest +19 -0
- package/dist/mod/cli-options.js +225 -0
- package/dist/mod/cli.js +519 -0
- package/dist/mod/index.js +3 -0
- package/dist/mod/llm-analysis.js +29 -0
- package/dist/mod/llm-output.js +742 -0
- package/dist/mod/module-graph-plugins.js +60 -0
- package/dist/mod/production-server.js +103 -0
- package/dist/mod/runtime-host.js +217 -0
- package/dist/mod/snapshot-history.js +73 -0
- package/dist/mod/types.js +3 -0
- package/dist/server/assets/__23tanstack-start-plugin-adapters-3QxJs4a0.js +5 -0
- package/dist/server/assets/_tanstack-start-manifest_v-DMytuIue.js +188 -0
- package/dist/server/assets/button-Bqnnid5i.js +41 -0
- package/dist/server/assets/colors-DhAxrYua.js +100 -0
- package/dist/server/assets/command-SdxShIbL.js +138 -0
- package/dist/server/assets/compare-BFMiiUsB.js +562 -0
- package/dist/server/assets/compare-CpOqTpYu.js +10 -0
- package/dist/server/assets/configure-Bvd45DTI.js +288 -0
- package/dist/server/assets/explorer-C7dODpSv.js +379 -0
- package/dist/server/assets/explorer-CpSb0JTa.js +20 -0
- package/dist/server/assets/flamegraph-CdW-VG6I.js +198 -0
- package/dist/server/assets/formatting-iDlL4tA-.js +4 -0
- package/dist/server/assets/graph-C1G9H5O4.js +438 -0
- package/dist/server/assets/graph-DAGFGioS.js +45 -0
- package/dist/server/assets/graph-command-menu-BV5GtOWx.js +249 -0
- package/dist/server/assets/hierarchy-B4K-Zfn9.js +16 -0
- package/dist/server/assets/hierarchy-BGpWSG-f.js +104 -0
- package/dist/server/assets/import-display-BVIOWcsm.js +124 -0
- package/dist/server/assets/imports-B6JBDl_h.js +379 -0
- package/dist/server/assets/imports-BGe5tZJT.js +28 -0
- package/dist/server/assets/input-C5r-hBix.js +19 -0
- package/dist/server/assets/loading-state-CrvCWTtw.js +23 -0
- package/dist/server/assets/modviz-data-CUyTorv0.js +197 -0
- package/dist/server/assets/modviz-layout-BAH2ogse.js +253 -0
- package/dist/server/assets/modviz-server-DoMlAyFW.js +195 -0
- package/dist/server/assets/modviz-sigma-XYxARWqd.js +1441 -0
- package/dist/server/assets/rolldown-runtime-rSIU-vHC.js +13 -0
- package/dist/server/assets/router-DYJ-zDbU.js +353 -0
- package/dist/server/assets/routes-DInCacpY.js +244 -0
- package/dist/server/assets/search-params-BNApPgkX.js +26 -0
- package/dist/server/assets/setup-view-DjI49Iqr.js +91 -0
- package/dist/server/assets/start-Ba3KII43.js +4 -0
- package/dist/server/assets/summary-z3lXkLCQ.js +208 -0
- package/dist/server/assets/tooltip-Ck0DDfF7.js +24 -0
- package/dist/server/assets/trace-ColKOf9g.js +16 -0
- package/dist/server/assets/trace-eVs-hIZO.js +578 -0
- package/dist/server/assets/treemap-BbZ9M4GF.js +17 -0
- package/dist/server/assets/treemap-CrgWFoCF.js +912 -0
- package/dist/server/assets/utils-BQZm0uva.js +8 -0
- package/dist/server/server.js +5259 -0
- package/dist/shared/modviz-compare.js +120 -0
- package/dist/shared/modviz-trace.js +244 -0
- package/package.json +135 -0
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
export function buildModvizLlmOutput(output) {
|
|
3
|
+
const metadata = output.metadata;
|
|
4
|
+
const nodeMap = new Map(output.nodes.map((node) => [node.path, node]));
|
|
5
|
+
const externalDependencies = output.nodes
|
|
6
|
+
.filter((node) => node.type === "external")
|
|
7
|
+
.map((node) => buildExternalDependencyReport(node, nodeMap, metadata));
|
|
8
|
+
const barrelFiles = output.nodes
|
|
9
|
+
.filter((node) => node.isBarrelFile && node.type !== "external")
|
|
10
|
+
.map((node) => buildBarrelFileReport(node, nodeMap, metadata));
|
|
11
|
+
const hotspots = output.nodes
|
|
12
|
+
.map((node) => buildHotspot(node, nodeMap, metadata))
|
|
13
|
+
.filter((node) => node.reachableModulesCount > 0 &&
|
|
14
|
+
(node.reachableModulesCount > 1 ||
|
|
15
|
+
node.reachableNodeModulesCount > 0 ||
|
|
16
|
+
node.directImporterCount > 1 ||
|
|
17
|
+
node.isBarrelFile))
|
|
18
|
+
.sort((left, right) => compareHotspots(right, left));
|
|
19
|
+
const externalPackages = buildExternalPackages(externalDependencies, metadata);
|
|
20
|
+
return {
|
|
21
|
+
format: "modviz-llm-v1",
|
|
22
|
+
metadata: {
|
|
23
|
+
...metadata,
|
|
24
|
+
pathFormat: "relative-to-basePath",
|
|
25
|
+
},
|
|
26
|
+
summary: {
|
|
27
|
+
totalNodes: output.nodes.length,
|
|
28
|
+
internalNodes: output.nodes.filter((node) => node.type !== "external").length,
|
|
29
|
+
barrelFiles: barrelFiles.length,
|
|
30
|
+
externalDependencies: externalDependencies.length,
|
|
31
|
+
externalPackages: externalPackages.length,
|
|
32
|
+
nodesWithMultipleOrigins: output.nodes.filter((node) => node.chain.length > 1).length,
|
|
33
|
+
topHotspots: hotspots.slice(0, 10).map((hotspot) => ({
|
|
34
|
+
path: hotspot.path,
|
|
35
|
+
displayPath: hotspot.displayPath,
|
|
36
|
+
reachableModulesCount: hotspot.reachableModulesCount,
|
|
37
|
+
reachableNodeModulesCount: hotspot.reachableNodeModulesCount,
|
|
38
|
+
directImporterCount: hotspot.directImporterCount,
|
|
39
|
+
isBarrelFile: hotspot.isBarrelFile,
|
|
40
|
+
})),
|
|
41
|
+
topExternalPackagesBySourceCount: externalPackages
|
|
42
|
+
.slice()
|
|
43
|
+
.sort((left, right) => {
|
|
44
|
+
if (right.sourceCount !== left.sourceCount) {
|
|
45
|
+
return right.sourceCount - left.sourceCount;
|
|
46
|
+
}
|
|
47
|
+
return right.modulePaths.length - left.modulePaths.length;
|
|
48
|
+
})
|
|
49
|
+
.slice(0, 10)
|
|
50
|
+
.map((pkg) => ({
|
|
51
|
+
packageName: pkg.packageName,
|
|
52
|
+
sourceCount: pkg.sourceCount,
|
|
53
|
+
moduleCount: pkg.modulePaths.length,
|
|
54
|
+
})),
|
|
55
|
+
},
|
|
56
|
+
hotspots,
|
|
57
|
+
barrelFiles,
|
|
58
|
+
externalDependencies,
|
|
59
|
+
externalPackages,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function renderModvizLlmMarkdown(report, options = {}) {
|
|
63
|
+
const auditHotspots = report.hotspots.filter((hotspot) => hotspot.type !== "entry" && hotspot.type !== "external");
|
|
64
|
+
const internalFanoutCulprits = report.hotspots.filter((hotspot) => hotspot.type !== "external" &&
|
|
65
|
+
hotspot.type !== "entry" &&
|
|
66
|
+
(hotspot.directImporterCount > 1 || hotspot.isBarrelFile) &&
|
|
67
|
+
(hotspot.reachableNodeModulesCount > 0 || hotspot.reachableModulesCount >= 25));
|
|
68
|
+
const multiSourcePackages = report.externalPackages.filter((pkg) => pkg.sourceGroupCount > 1);
|
|
69
|
+
const lines = [
|
|
70
|
+
"# modviz LLM report",
|
|
71
|
+
"",
|
|
72
|
+
...(options.focus?.packageName || options.focus?.nodeQuery
|
|
73
|
+
? [
|
|
74
|
+
"## Active focus",
|
|
75
|
+
...(options.focus.packageName ? [`- Package query: ${options.focus.packageName}`] : []),
|
|
76
|
+
...(options.focus.nodeQuery ? [`- Node query: ${options.focus.nodeQuery}`] : []),
|
|
77
|
+
"",
|
|
78
|
+
]
|
|
79
|
+
: []),
|
|
80
|
+
"## Summary",
|
|
81
|
+
`- Total nodes: ${report.summary.totalNodes}`,
|
|
82
|
+
`- Internal nodes: ${report.summary.internalNodes}`,
|
|
83
|
+
`- Explicit barrel files: ${report.summary.barrelFiles}`,
|
|
84
|
+
`- External dependency modules: ${report.summary.externalDependencies}`,
|
|
85
|
+
`- External packages: ${report.summary.externalPackages}`,
|
|
86
|
+
`- Nodes with multiple origin chains: ${report.summary.nodesWithMultipleOrigins}`,
|
|
87
|
+
"",
|
|
88
|
+
"## Import triggers to audit",
|
|
89
|
+
];
|
|
90
|
+
if (report.hotspots.length === 0) {
|
|
91
|
+
lines.push("- None");
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
for (const hotspot of auditHotspots.slice(0, 10)) {
|
|
95
|
+
const kindLabel = hotspot.isBarrelFile ? `explicit barrel, ${hotspot.type}` : hotspot.type;
|
|
96
|
+
lines.push(`- ${hotspot.displayPath} (${kindLabel}) reaches ${hotspot.reachableModulesCount} modules, including ${hotspot.reachableNodeModulesCount} node_modules modules; direct importers: ${formatPreviewList(hotspot.directImporters.map((importer) => formatPathForLlm(importer, report.metadata)), 5)}`);
|
|
97
|
+
if (hotspot.topExternalPackages.length > 0) {
|
|
98
|
+
lines.push(` Pulls in: ${formatPreviewList(hotspot.topExternalPackages, 5)}`);
|
|
99
|
+
}
|
|
100
|
+
if (hotspot.signals.length > 0) {
|
|
101
|
+
lines.push(` Signals: ${formatPreviewList(hotspot.signals, 4)}`);
|
|
102
|
+
}
|
|
103
|
+
if (hotspot.originChains.length > 0) {
|
|
104
|
+
lines.push(` Origin chains: ${hotspot.originChains
|
|
105
|
+
.slice(0, 2)
|
|
106
|
+
.map((chain) => formatChainForLlm(chain, report.metadata))
|
|
107
|
+
.join(" | ")}${hotspot.originChains.length > 2 ? ` | +${hotspot.originChains.length - 2} more` : ""}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
lines.push("", "## Internal fan-out culprits");
|
|
112
|
+
if (internalFanoutCulprits.length === 0) {
|
|
113
|
+
lines.push("- None");
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
for (const hotspot of internalFanoutCulprits.slice(0, 10)) {
|
|
117
|
+
lines.push(`- ${hotspot.displayPath}${hotspot.isBarrelFile ? " [explicit barrel]" : ""} reaches ${hotspot.reachableModulesCount} modules, including ${hotspot.reachableNodeModulesCount} node_modules modules; direct importers: ${formatPreviewList(hotspot.directImporters.map((importer) => formatPathForLlm(importer, report.metadata)), 5)}`);
|
|
118
|
+
if (hotspot.topExternalPackages.length > 0) {
|
|
119
|
+
lines.push(` Pulls in: ${formatPreviewList(hotspot.topExternalPackages, 5)}`);
|
|
120
|
+
}
|
|
121
|
+
lines.push(` Signals: ${formatPreviewList(hotspot.signals, 4)}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
lines.push("", "## node_modules with multiple sources");
|
|
125
|
+
if (multiSourcePackages.length === 0) {
|
|
126
|
+
lines.push("- None");
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
for (const pkg of multiSourcePackages.slice(0, 10)) {
|
|
130
|
+
const sourceSummary = pkg.sourceGroupCount === pkg.sourceCount
|
|
131
|
+
? `${pkg.sourceCount} sources`
|
|
132
|
+
: `${pkg.sourceCount} source files across ${pkg.sourceGroupCount} source groups`;
|
|
133
|
+
lines.push(`- ${pkg.packageName} is introduced by ${sourceSummary}`);
|
|
134
|
+
if (pkg.sourceGroups.length > 0) {
|
|
135
|
+
lines.push(` Top source groups: ${formatSourceGroupPreview(pkg.sourceGroups, 6)}`);
|
|
136
|
+
}
|
|
137
|
+
if (pkg.barrelSources.length > 0) {
|
|
138
|
+
const fanoutSources = buildSourceGroups(pkg.barrelSources, report.metadata).map((sourceGroup) => sourceGroup.label);
|
|
139
|
+
lines.push(` Fan-out sources: ${formatPreviewList(fanoutSources, 5)}`);
|
|
140
|
+
}
|
|
141
|
+
if (pkg.modulePaths.length > 0) {
|
|
142
|
+
lines.push(` Representative modules: ${formatPreviewList(pkg.modulePaths
|
|
143
|
+
.slice(0, 3)
|
|
144
|
+
.map((modulePath) => formatModulePathWithinPackage(modulePath, pkg.packageName, report.metadata)), 3)}`);
|
|
145
|
+
}
|
|
146
|
+
if (pkg.originChains.length > 0) {
|
|
147
|
+
lines.push(` Origin chains: ${pkg.originChains
|
|
148
|
+
.slice(0, 2)
|
|
149
|
+
.map((chain) => formatChainForLlm(chain, report.metadata))
|
|
150
|
+
.join(" | ")}${pkg.originChains.length > 2 ? ` | +${pkg.originChains.length - 2} more` : ""}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (options.commandHints) {
|
|
155
|
+
lines.push("", "## Follow-up commands", `- Inspect one package: ${options.commandHints.packageCommand}`, `- Inspect one node: ${options.commandHints.nodeCommand}`);
|
|
156
|
+
}
|
|
157
|
+
if (options.focusedDrilldown) {
|
|
158
|
+
lines.push("", "## Focused drilldown", ...trimTrailingBlankLines(options.focusedDrilldown
|
|
159
|
+
.split("\n")
|
|
160
|
+
.filter((line, index) => !(index === 0 && line.trim() === "# modviz LLM drilldown"))));
|
|
161
|
+
}
|
|
162
|
+
return `${lines.join("\n")}\n`;
|
|
163
|
+
}
|
|
164
|
+
export function renderModvizLlmDrilldown(report, options) {
|
|
165
|
+
const limit = Math.max(options.limit ?? 20, 1);
|
|
166
|
+
const lines = ["# modviz LLM drilldown", ""];
|
|
167
|
+
let renderedSectionCount = 0;
|
|
168
|
+
if (options.packageName) {
|
|
169
|
+
const packageMatches = findExternalPackageMatches(report.externalPackages, options.packageName);
|
|
170
|
+
if (packageMatches.length === 0) {
|
|
171
|
+
lines.push(`## Package: ${options.packageName}`, "- No matching package");
|
|
172
|
+
}
|
|
173
|
+
else if (packageMatches.length > 1) {
|
|
174
|
+
lines.push(`## Package: ${options.packageName}`, `- Multiple matches: ${formatPreviewList(packageMatches.map((pkg) => pkg.packageName), limit)}`);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
renderPackageDrilldown(lines, packageMatches[0], report.metadata, limit);
|
|
178
|
+
renderedSectionCount += 1;
|
|
179
|
+
}
|
|
180
|
+
lines.push("");
|
|
181
|
+
}
|
|
182
|
+
if (options.nodeQuery) {
|
|
183
|
+
const nodeMatches = findNodeMatches(report, options.nodeQuery);
|
|
184
|
+
if (nodeMatches.length === 0) {
|
|
185
|
+
lines.push(`## Node: ${options.nodeQuery}`, "- No matching node");
|
|
186
|
+
}
|
|
187
|
+
else if (nodeMatches.length > 1) {
|
|
188
|
+
lines.push(`## Node: ${options.nodeQuery}`, `- Multiple matches: ${formatPreviewList(nodeMatches.map((node) => node.displayPath), limit)}`);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
renderNodeDrilldown(lines, nodeMatches[0], report.metadata, limit);
|
|
192
|
+
renderedSectionCount += 1;
|
|
193
|
+
}
|
|
194
|
+
lines.push("");
|
|
195
|
+
}
|
|
196
|
+
if (renderedSectionCount === 0 && !options.packageName && !options.nodeQuery) {
|
|
197
|
+
lines.push("- No drilldown query provided", "");
|
|
198
|
+
}
|
|
199
|
+
return `${trimTrailingBlankLines(lines).join("\n")}\n`;
|
|
200
|
+
}
|
|
201
|
+
export function resolveModvizFocus(report, options) {
|
|
202
|
+
const includedPaths = new Set();
|
|
203
|
+
const matchedPackageNames = options.packageName
|
|
204
|
+
? findExternalPackageMatches(report.externalPackages, options.packageName).map((pkg) => pkg.packageName)
|
|
205
|
+
: [];
|
|
206
|
+
const matchedNodePaths = options.nodeQuery
|
|
207
|
+
? findNodeMatches(report, options.nodeQuery).map((node) => node.path)
|
|
208
|
+
: [];
|
|
209
|
+
for (const packageName of matchedPackageNames) {
|
|
210
|
+
const pkg = report.externalPackages.find((externalPackage) => externalPackage.packageName === packageName);
|
|
211
|
+
if (!pkg) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
for (const modulePath of pkg.modulePaths) {
|
|
215
|
+
includedPaths.add(modulePath);
|
|
216
|
+
}
|
|
217
|
+
for (const source of pkg.sources) {
|
|
218
|
+
includedPaths.add(source);
|
|
219
|
+
}
|
|
220
|
+
for (const source of pkg.barrelSources) {
|
|
221
|
+
includedPaths.add(source);
|
|
222
|
+
}
|
|
223
|
+
for (const chain of pkg.originChains) {
|
|
224
|
+
for (const nodePath of chain) {
|
|
225
|
+
includedPaths.add(nodePath);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
for (const nodePath of matchedNodePaths) {
|
|
230
|
+
includedPaths.add(nodePath);
|
|
231
|
+
const node = findNodeMatches(report, nodePath)[0];
|
|
232
|
+
if (!node) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const directImporters = node.hotspot?.directImporters ?? node.externalDependency?.directImporters ?? [];
|
|
236
|
+
for (const importer of directImporters) {
|
|
237
|
+
includedPaths.add(importer);
|
|
238
|
+
}
|
|
239
|
+
const originChains = node.hotspot?.originChains ??
|
|
240
|
+
node.barrel?.originChains ??
|
|
241
|
+
node.externalDependency?.originChains ??
|
|
242
|
+
[];
|
|
243
|
+
for (const chain of originChains) {
|
|
244
|
+
for (const chainNode of chain) {
|
|
245
|
+
includedPaths.add(chainNode);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
matchedPackageNames,
|
|
251
|
+
matchedNodePaths,
|
|
252
|
+
includedPaths: Array.from(includedPaths),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
export function inferLlmOutputPaths(outputFile) {
|
|
256
|
+
const parsed = path.parse(outputFile);
|
|
257
|
+
const llmBase = path.join(parsed.dir, `${parsed.name}.llm`);
|
|
258
|
+
return {
|
|
259
|
+
json: `${llmBase}.json`,
|
|
260
|
+
markdown: `${llmBase}.md`,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function buildHotspot(node, nodeMap, metadata) {
|
|
264
|
+
const basePath = metadata.basePath;
|
|
265
|
+
const reachablePaths = collectReachablePaths(node, nodeMap);
|
|
266
|
+
const reachableNodes = Array.from(reachablePaths)
|
|
267
|
+
.map((nodePath) => nodeMap.get(nodePath))
|
|
268
|
+
.filter(Boolean);
|
|
269
|
+
const reachableNodeModulesCount = reachableNodes.filter((reachableNode) => reachableNode.type === "external").length;
|
|
270
|
+
const reachableBarrelFilesCount = reachableNodes.filter((reachableNode) => reachableNode.isBarrelFile).length;
|
|
271
|
+
const signals = [];
|
|
272
|
+
if (node.isBarrelFile) {
|
|
273
|
+
signals.push("barrel-file");
|
|
274
|
+
}
|
|
275
|
+
if (reachableNodeModulesCount > 0) {
|
|
276
|
+
signals.push(`pulls ${reachableNodeModulesCount} node_modules modules transitively`);
|
|
277
|
+
}
|
|
278
|
+
if (node.importedBy.length > 1) {
|
|
279
|
+
signals.push(`shared by ${node.importedBy.length} direct importers`);
|
|
280
|
+
}
|
|
281
|
+
if (reachablePaths.size > node.importees.length + 5) {
|
|
282
|
+
signals.push(`fans out to ${reachablePaths.size} transitive modules`);
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
path: toDisplayPath(node.path, basePath),
|
|
286
|
+
displayPath: formatPathForLlm(node.path, metadata),
|
|
287
|
+
type: node.type,
|
|
288
|
+
isBarrelFile: node.isBarrelFile,
|
|
289
|
+
directImporterCount: node.importedBy.length,
|
|
290
|
+
directImporters: sortStrings(node.importedBy.map((importer) => toDisplayPath(importer, basePath))),
|
|
291
|
+
directImporteeCount: node.importees.length,
|
|
292
|
+
originChains: normalizeChains(node.chain, basePath),
|
|
293
|
+
reachableModulesCount: reachablePaths.size,
|
|
294
|
+
reachableInternalModulesCount: reachableNodes.filter((reachableNode) => reachableNode.type !== "external").length,
|
|
295
|
+
reachableNodeModulesCount,
|
|
296
|
+
reachableBarrelFilesCount,
|
|
297
|
+
topExternalPackages: collectExternalPackageNames(reachableNodes),
|
|
298
|
+
signals,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function buildBarrelFileReport(node, nodeMap, metadata) {
|
|
302
|
+
const basePath = metadata.basePath;
|
|
303
|
+
const reachablePaths = collectReachablePaths(node, nodeMap);
|
|
304
|
+
const reachableNodes = Array.from(reachablePaths)
|
|
305
|
+
.map((nodePath) => nodeMap.get(nodePath))
|
|
306
|
+
.filter(Boolean);
|
|
307
|
+
const nodeModulesIntroduced = reachableNodes
|
|
308
|
+
.filter((reachableNode) => reachableNode.type === "external")
|
|
309
|
+
.map((dependency) => ({
|
|
310
|
+
path: toDisplayPath(dependency.path, basePath),
|
|
311
|
+
packageName: getPackageNameFromNodeModulesPath(dependency.path),
|
|
312
|
+
chainsFromBarrel: normalizeChains(dependency.chain
|
|
313
|
+
.filter((chain) => chain.includes(node.path))
|
|
314
|
+
.map((chain) => chain.slice(chain.indexOf(node.path))), basePath),
|
|
315
|
+
}))
|
|
316
|
+
.sort((left, right) => left.path.localeCompare(right.path));
|
|
317
|
+
return {
|
|
318
|
+
path: toDisplayPath(node.path, basePath),
|
|
319
|
+
displayPath: formatPathForLlm(node.path, metadata),
|
|
320
|
+
directImporterCount: node.importedBy.length,
|
|
321
|
+
directImporters: sortStrings(node.importedBy.map((importer) => toDisplayPath(importer, basePath))),
|
|
322
|
+
originChains: normalizeChains(node.chain, basePath),
|
|
323
|
+
impact: {
|
|
324
|
+
directImporteeCount: node.importees.length,
|
|
325
|
+
reachableModulesCount: reachablePaths.size,
|
|
326
|
+
reachableInternalModulesCount: reachableNodes.filter((reachableNode) => reachableNode.type !== "external").length,
|
|
327
|
+
reachableNodeModulesCount: reachableNodes.filter((reachableNode) => reachableNode.type === "external").length,
|
|
328
|
+
reachableBarrelFilesCount: reachableNodes.filter((reachableNode) => reachableNode.isBarrelFile).length,
|
|
329
|
+
},
|
|
330
|
+
nodeModulesIntroduced,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
function buildExternalDependencyReport(node, nodeMap, metadata) {
|
|
334
|
+
const basePath = metadata.basePath;
|
|
335
|
+
const directImporters = sortStrings(node.importedBy.map((importer) => toDisplayPath(importer, basePath)));
|
|
336
|
+
const originChains = normalizeChains(node.chain, basePath);
|
|
337
|
+
const introducedThrough = directImporters.map((importer) => ({
|
|
338
|
+
path: importer,
|
|
339
|
+
originChains: originChains.filter((chain) => chain.at(-2) === importer),
|
|
340
|
+
}));
|
|
341
|
+
const barrelSources = sortStrings(Array.from(new Set(node.chain
|
|
342
|
+
.flatMap((chain) => chain.filter((nodePath) => nodeMap.get(nodePath)?.isBarrelFile))
|
|
343
|
+
.filter((nodePath) => nodePath !== node.path)
|
|
344
|
+
.map((nodePath) => toDisplayPath(nodePath, basePath)))));
|
|
345
|
+
return {
|
|
346
|
+
path: toDisplayPath(node.path, basePath),
|
|
347
|
+
displayPath: formatPathForLlm(node.path, metadata),
|
|
348
|
+
packageName: getPackageNameFromNodeModulesPath(node.path),
|
|
349
|
+
directImporterCount: directImporters.length,
|
|
350
|
+
directImporters,
|
|
351
|
+
originChains,
|
|
352
|
+
introducedThrough,
|
|
353
|
+
barrelSources,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
function buildExternalPackages(externalDependencies, metadata) {
|
|
357
|
+
const packageMap = new Map();
|
|
358
|
+
for (const dependency of externalDependencies) {
|
|
359
|
+
const packageName = dependency.packageName ?? dependency.path;
|
|
360
|
+
const current = packageMap.get(packageName);
|
|
361
|
+
if (!current) {
|
|
362
|
+
const sourceGroups = buildSourceGroups(dependency.directImporters, metadata);
|
|
363
|
+
packageMap.set(packageName, {
|
|
364
|
+
packageName,
|
|
365
|
+
modulePaths: [dependency.path],
|
|
366
|
+
sourceCount: dependency.directImporters.length,
|
|
367
|
+
sourceGroupCount: sourceGroups.length,
|
|
368
|
+
sources: [...dependency.directImporters],
|
|
369
|
+
originChains: [...dependency.originChains],
|
|
370
|
+
barrelSources: [...dependency.barrelSources],
|
|
371
|
+
sourceGroups,
|
|
372
|
+
});
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
current.modulePaths = sortStrings([...current.modulePaths, dependency.path]);
|
|
376
|
+
current.sources = sortStrings([...current.sources, ...dependency.directImporters]);
|
|
377
|
+
current.sourceCount = current.sources.length;
|
|
378
|
+
current.sourceGroups = buildSourceGroups(current.sources, metadata);
|
|
379
|
+
current.sourceGroupCount = current.sourceGroups.length;
|
|
380
|
+
current.originChains = dedupeChains([...current.originChains, ...dependency.originChains]);
|
|
381
|
+
current.barrelSources = sortStrings([...current.barrelSources, ...dependency.barrelSources]);
|
|
382
|
+
}
|
|
383
|
+
return Array.from(packageMap.values()).sort((left, right) => {
|
|
384
|
+
if (right.sourceCount !== left.sourceCount) {
|
|
385
|
+
return right.sourceCount - left.sourceCount;
|
|
386
|
+
}
|
|
387
|
+
return left.packageName.localeCompare(right.packageName);
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
function collectReachablePaths(startNode, nodeMap) {
|
|
391
|
+
const reachable = new Set();
|
|
392
|
+
const stack = [...startNode.importees];
|
|
393
|
+
while (stack.length > 0) {
|
|
394
|
+
const currentPath = stack.pop();
|
|
395
|
+
if (!currentPath || reachable.has(currentPath) || currentPath === startNode.path) {
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
const currentNode = nodeMap.get(currentPath);
|
|
399
|
+
if (!currentNode) {
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
reachable.add(currentPath);
|
|
403
|
+
for (const importee of currentNode.importees) {
|
|
404
|
+
if (!reachable.has(importee)) {
|
|
405
|
+
stack.push(importee);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return reachable;
|
|
410
|
+
}
|
|
411
|
+
function normalizeChains(chains, basePath) {
|
|
412
|
+
return dedupeChains(chains.map((chain) => chain.map((nodePath) => toDisplayPath(nodePath, basePath))));
|
|
413
|
+
}
|
|
414
|
+
function dedupeChains(chains) {
|
|
415
|
+
const seen = new Set();
|
|
416
|
+
const deduped = [];
|
|
417
|
+
for (const chain of chains) {
|
|
418
|
+
const key = chain.join("\u0000");
|
|
419
|
+
if (seen.has(key)) {
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
seen.add(key);
|
|
423
|
+
deduped.push(chain);
|
|
424
|
+
}
|
|
425
|
+
return deduped;
|
|
426
|
+
}
|
|
427
|
+
function compareHotspots(left, right) {
|
|
428
|
+
if (left.reachableNodeModulesCount !== right.reachableNodeModulesCount) {
|
|
429
|
+
return left.reachableNodeModulesCount - right.reachableNodeModulesCount;
|
|
430
|
+
}
|
|
431
|
+
if (left.reachableModulesCount !== right.reachableModulesCount) {
|
|
432
|
+
return left.reachableModulesCount - right.reachableModulesCount;
|
|
433
|
+
}
|
|
434
|
+
if (left.directImporterCount !== right.directImporterCount) {
|
|
435
|
+
return left.directImporterCount - right.directImporterCount;
|
|
436
|
+
}
|
|
437
|
+
if (left.isBarrelFile !== right.isBarrelFile) {
|
|
438
|
+
return Number(left.isBarrelFile) - Number(right.isBarrelFile);
|
|
439
|
+
}
|
|
440
|
+
return left.path.localeCompare(right.path);
|
|
441
|
+
}
|
|
442
|
+
function toDisplayPath(filePath, basePath) {
|
|
443
|
+
if (!path.isAbsolute(filePath)) {
|
|
444
|
+
return filePath;
|
|
445
|
+
}
|
|
446
|
+
const relativePath = path.relative(basePath, filePath);
|
|
447
|
+
return relativePath && !relativePath.startsWith("..") ? relativePath : filePath;
|
|
448
|
+
}
|
|
449
|
+
function getPackageNameFromNodeModulesPath(filePath) {
|
|
450
|
+
const parts = filePath.split(/[\\/]/g);
|
|
451
|
+
const nodeModulesIndex = parts.lastIndexOf("node_modules");
|
|
452
|
+
if (nodeModulesIndex === -1) {
|
|
453
|
+
return undefined;
|
|
454
|
+
}
|
|
455
|
+
const firstPart = parts[nodeModulesIndex + 1];
|
|
456
|
+
if (!firstPart) {
|
|
457
|
+
return undefined;
|
|
458
|
+
}
|
|
459
|
+
if (firstPart.startsWith("@")) {
|
|
460
|
+
const secondPart = parts[nodeModulesIndex + 2];
|
|
461
|
+
return secondPart ? `${firstPart}/${secondPart}` : firstPart;
|
|
462
|
+
}
|
|
463
|
+
return firstPart;
|
|
464
|
+
}
|
|
465
|
+
function collectExternalPackageNames(nodes) {
|
|
466
|
+
const counts = new Map();
|
|
467
|
+
for (const node of nodes) {
|
|
468
|
+
if (node.type !== "external") {
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
const packageName = getPackageNameFromNodeModulesPath(node.path);
|
|
472
|
+
if (!packageName) {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
counts.set(packageName, (counts.get(packageName) ?? 0) + 1);
|
|
476
|
+
}
|
|
477
|
+
return Array.from(counts.entries())
|
|
478
|
+
.sort((left, right) => {
|
|
479
|
+
if (right[1] !== left[1]) {
|
|
480
|
+
return right[1] - left[1];
|
|
481
|
+
}
|
|
482
|
+
return left[0].localeCompare(right[0]);
|
|
483
|
+
})
|
|
484
|
+
.map(([packageName]) => packageName);
|
|
485
|
+
}
|
|
486
|
+
function buildSourceGroups(sources, metadata) {
|
|
487
|
+
const groups = new Map();
|
|
488
|
+
for (const source of sortStrings(sources)) {
|
|
489
|
+
const workspacePackage = getWorkspacePackageForDisplayPath(source, metadata);
|
|
490
|
+
const externalPackage = getPackageNameFromNodeModulesPath(source);
|
|
491
|
+
const label = workspacePackage?.name ?? externalPackage ?? source;
|
|
492
|
+
const kind = workspacePackage
|
|
493
|
+
? "workspace-package"
|
|
494
|
+
: externalPackage
|
|
495
|
+
? "external-package"
|
|
496
|
+
: "file";
|
|
497
|
+
const current = groups.get(label);
|
|
498
|
+
if (!current) {
|
|
499
|
+
groups.set(label, {
|
|
500
|
+
kind,
|
|
501
|
+
label,
|
|
502
|
+
paths: [source],
|
|
503
|
+
});
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
current.paths = sortStrings([...current.paths, source]);
|
|
507
|
+
}
|
|
508
|
+
return Array.from(groups.values()).sort(compareSourceGroups);
|
|
509
|
+
}
|
|
510
|
+
function formatChainForLlm(chain, metadata) {
|
|
511
|
+
return chain.map((nodePath) => formatPathForLlm(nodePath, metadata)).join(" -> ");
|
|
512
|
+
}
|
|
513
|
+
function formatPathForLlm(filePath, metadata) {
|
|
514
|
+
const displayPath = toDisplayPath(filePath, metadata.basePath);
|
|
515
|
+
const workspacePackage = getWorkspacePackageForDisplayPath(displayPath, metadata);
|
|
516
|
+
if (workspacePackage) {
|
|
517
|
+
return `${workspacePackage.name} (${displayPath})`;
|
|
518
|
+
}
|
|
519
|
+
const externalPackageName = getPackageNameFromNodeModulesPath(displayPath);
|
|
520
|
+
if (externalPackageName && externalPackageName !== displayPath) {
|
|
521
|
+
return `${externalPackageName} (${displayPath})`;
|
|
522
|
+
}
|
|
523
|
+
return displayPath;
|
|
524
|
+
}
|
|
525
|
+
function getWorkspacePackageForDisplayPath(displayPath, metadata) {
|
|
526
|
+
const normalizedDisplayPath = normalizePath(displayPath);
|
|
527
|
+
const packages = metadata.packages
|
|
528
|
+
.map((pkg) => ({
|
|
529
|
+
...pkg,
|
|
530
|
+
normalizedPath: normalizePath(pkg.path),
|
|
531
|
+
}))
|
|
532
|
+
.filter((pkg) => normalizedDisplayPath === pkg.normalizedPath ||
|
|
533
|
+
normalizedDisplayPath.startsWith(`${pkg.normalizedPath}/`))
|
|
534
|
+
.sort((left, right) => right.normalizedPath.length - left.normalizedPath.length);
|
|
535
|
+
return packages[0];
|
|
536
|
+
}
|
|
537
|
+
function normalizePath(filePath) {
|
|
538
|
+
return filePath.replace(/\\/g, "/").replace(/\/$/, "");
|
|
539
|
+
}
|
|
540
|
+
function sortStrings(values) {
|
|
541
|
+
return Array.from(new Set(values)).sort((left, right) => left.localeCompare(right));
|
|
542
|
+
}
|
|
543
|
+
function formatPreviewList(values, limit) {
|
|
544
|
+
const uniqueValues = Array.from(new Set(values));
|
|
545
|
+
if (uniqueValues.length === 0) {
|
|
546
|
+
return "none";
|
|
547
|
+
}
|
|
548
|
+
const preview = uniqueValues.slice(0, limit).join(", ");
|
|
549
|
+
return uniqueValues.length > limit ? `${preview}, +${uniqueValues.length - limit} more` : preview;
|
|
550
|
+
}
|
|
551
|
+
function formatSourceGroupPreview(sourceGroups, limit) {
|
|
552
|
+
return formatPreviewList(sourceGroups.map((group) => group.paths.length > 1 ? `${group.label} (${group.paths.length} files)` : group.label), limit);
|
|
553
|
+
}
|
|
554
|
+
function formatModulePathWithinPackage(modulePath, packageName, metadata) {
|
|
555
|
+
const displayPath = toDisplayPath(modulePath, metadata.basePath);
|
|
556
|
+
const parts = normalizePath(displayPath).split("/");
|
|
557
|
+
const nodeModulesIndex = parts.lastIndexOf("node_modules");
|
|
558
|
+
if (nodeModulesIndex === -1) {
|
|
559
|
+
return displayPath;
|
|
560
|
+
}
|
|
561
|
+
const packageParts = packageName.startsWith("@") ? packageName.split("/") : [packageName];
|
|
562
|
+
const startIndex = nodeModulesIndex + 1 + packageParts.length;
|
|
563
|
+
const withinPackage = parts.slice(startIndex).join("/");
|
|
564
|
+
return withinPackage || packageName;
|
|
565
|
+
}
|
|
566
|
+
function findExternalPackageMatches(packages, query) {
|
|
567
|
+
const normalizedQuery = normalizeSearchQuery(query);
|
|
568
|
+
const exactMatches = packages.filter((pkg) => normalizeSearchQuery(pkg.packageName) === normalizedQuery);
|
|
569
|
+
if (exactMatches.length > 0) {
|
|
570
|
+
return exactMatches;
|
|
571
|
+
}
|
|
572
|
+
return packages.filter((pkg) => normalizeSearchQuery(pkg.packageName).includes(normalizedQuery));
|
|
573
|
+
}
|
|
574
|
+
function findNodeMatches(report, query) {
|
|
575
|
+
const nodesByPath = new Map();
|
|
576
|
+
for (const hotspot of report.hotspots) {
|
|
577
|
+
nodesByPath.set(hotspot.path, {
|
|
578
|
+
path: hotspot.path,
|
|
579
|
+
displayPath: hotspot.displayPath,
|
|
580
|
+
hotspot,
|
|
581
|
+
barrel: report.barrelFiles.find((barrel) => barrel.path === hotspot.path),
|
|
582
|
+
externalDependency: report.externalDependencies.find((dependency) => dependency.path === hotspot.path),
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
for (const barrel of report.barrelFiles) {
|
|
586
|
+
const existing = nodesByPath.get(barrel.path);
|
|
587
|
+
if (existing) {
|
|
588
|
+
existing.barrel = barrel;
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
nodesByPath.set(barrel.path, {
|
|
592
|
+
path: barrel.path,
|
|
593
|
+
displayPath: barrel.displayPath,
|
|
594
|
+
barrel,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
for (const dependency of report.externalDependencies) {
|
|
598
|
+
const existing = nodesByPath.get(dependency.path);
|
|
599
|
+
if (existing) {
|
|
600
|
+
existing.externalDependency = dependency;
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
nodesByPath.set(dependency.path, {
|
|
604
|
+
path: dependency.path,
|
|
605
|
+
displayPath: dependency.displayPath,
|
|
606
|
+
externalDependency: dependency,
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
const normalizedQuery = normalizeSearchQuery(query);
|
|
610
|
+
const nodes = Array.from(nodesByPath.values());
|
|
611
|
+
const exactMatches = nodes.filter((node) => buildNodeSearchTerms(node).some((term) => normalizeSearchQuery(term) === normalizedQuery));
|
|
612
|
+
if (exactMatches.length > 0) {
|
|
613
|
+
return exactMatches;
|
|
614
|
+
}
|
|
615
|
+
return nodes.filter((node) => buildNodeSearchTerms(node).some((term) => normalizeSearchQuery(term).includes(normalizedQuery)));
|
|
616
|
+
}
|
|
617
|
+
function buildNodeSearchTerms(node) {
|
|
618
|
+
return [node.path, node.displayPath, node.externalDependency?.packageName]
|
|
619
|
+
.filter(Boolean)
|
|
620
|
+
.map((term) => term);
|
|
621
|
+
}
|
|
622
|
+
function renderPackageDrilldown(lines, pkg, metadata, limit) {
|
|
623
|
+
lines.push(`## Package: ${pkg.packageName}`);
|
|
624
|
+
lines.push(`- Source files: ${pkg.sourceCount}`);
|
|
625
|
+
lines.push(`- Source groups: ${pkg.sourceGroupCount}`);
|
|
626
|
+
lines.push(`- Matched external modules: ${pkg.modulePaths.length}`);
|
|
627
|
+
if (pkg.barrelSources.length > 0) {
|
|
628
|
+
lines.push(`- Fan-out sources: ${formatPreviewList(buildSourceGroups(pkg.barrelSources, metadata).map((group) => group.label), limit)}`);
|
|
629
|
+
}
|
|
630
|
+
lines.push("", "### Source groups");
|
|
631
|
+
for (const group of pkg.sourceGroups.slice(0, limit)) {
|
|
632
|
+
lines.push(`- ${group.label} (${group.paths.length} file${group.paths.length === 1 ? "" : "s"}): ${formatPreviewList(group.paths, limit)}`);
|
|
633
|
+
}
|
|
634
|
+
if (pkg.sourceGroups.length > limit) {
|
|
635
|
+
lines.push(`- +${pkg.sourceGroups.length - limit} more source groups`);
|
|
636
|
+
}
|
|
637
|
+
lines.push("", "### Representative modules");
|
|
638
|
+
for (const modulePath of pkg.modulePaths.slice(0, limit)) {
|
|
639
|
+
lines.push(`- ${formatModulePathWithinPackage(modulePath, pkg.packageName, metadata)}`);
|
|
640
|
+
}
|
|
641
|
+
if (pkg.modulePaths.length > limit) {
|
|
642
|
+
lines.push(`- +${pkg.modulePaths.length - limit} more modules`);
|
|
643
|
+
}
|
|
644
|
+
lines.push("", "### Origin chains");
|
|
645
|
+
for (const chain of pkg.originChains.slice(0, limit)) {
|
|
646
|
+
lines.push(`- ${formatChainForLlm(chain, metadata)}`);
|
|
647
|
+
}
|
|
648
|
+
if (pkg.originChains.length > limit) {
|
|
649
|
+
lines.push(`- +${pkg.originChains.length - limit} more origin chains`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
function renderNodeDrilldown(lines, node, metadata, limit) {
|
|
653
|
+
lines.push(`## Node: ${node.displayPath}`);
|
|
654
|
+
if (node.hotspot) {
|
|
655
|
+
lines.push(`- Type: ${node.hotspot.type}`);
|
|
656
|
+
lines.push(`- Reachable modules: ${node.hotspot.reachableModulesCount}`);
|
|
657
|
+
lines.push(`- Reachable node_modules modules: ${node.hotspot.reachableNodeModulesCount}`);
|
|
658
|
+
lines.push(`- Direct importers: ${node.hotspot.directImporterCount}`);
|
|
659
|
+
lines.push(`- Direct importees: ${node.hotspot.directImporteeCount}`);
|
|
660
|
+
if (node.hotspot.topExternalPackages.length > 0) {
|
|
661
|
+
lines.push(`- Top external packages: ${formatPreviewList(node.hotspot.topExternalPackages, limit)}`);
|
|
662
|
+
}
|
|
663
|
+
if (node.hotspot.signals.length > 0) {
|
|
664
|
+
lines.push(`- Signals: ${formatPreviewList(node.hotspot.signals, limit)}`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
if (node.barrel) {
|
|
668
|
+
lines.push(`- Explicit barrel file: yes`);
|
|
669
|
+
lines.push(`- node_modules introduced: ${node.barrel.nodeModulesIntroduced.length}`);
|
|
670
|
+
}
|
|
671
|
+
if (node.externalDependency) {
|
|
672
|
+
lines.push(`- External package: ${node.externalDependency.packageName ?? node.externalDependency.path}`);
|
|
673
|
+
if (node.externalDependency.barrelSources.length > 0) {
|
|
674
|
+
lines.push(`- Fan-out sources: ${formatPreviewList(node.externalDependency.barrelSources, limit)}`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (node.hotspot?.directImporters.length || node.externalDependency?.directImporters.length) {
|
|
678
|
+
const importers = node.hotspot?.directImporters ?? node.externalDependency?.directImporters ?? [];
|
|
679
|
+
lines.push("", "### Direct importers");
|
|
680
|
+
for (const importer of importers.slice(0, limit)) {
|
|
681
|
+
lines.push(`- ${importer}`);
|
|
682
|
+
}
|
|
683
|
+
if (importers.length > limit) {
|
|
684
|
+
lines.push(`- +${importers.length - limit} more importers`);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (node.externalDependency?.introducedThrough.length) {
|
|
688
|
+
lines.push("", "### Introduced through");
|
|
689
|
+
for (const source of node.externalDependency.introducedThrough.slice(0, limit)) {
|
|
690
|
+
lines.push(`- ${source.path}: ${source.originChains.length} chain${source.originChains.length === 1 ? "" : "s"}`);
|
|
691
|
+
}
|
|
692
|
+
if (node.externalDependency.introducedThrough.length > limit) {
|
|
693
|
+
lines.push(`- +${node.externalDependency.introducedThrough.length - limit} more introducers`);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
const originChains = node.hotspot?.originChains ??
|
|
697
|
+
node.barrel?.originChains ??
|
|
698
|
+
node.externalDependency?.originChains ??
|
|
699
|
+
[];
|
|
700
|
+
if (originChains.length > 0) {
|
|
701
|
+
lines.push("", "### Origin chains");
|
|
702
|
+
for (const chain of originChains.slice(0, limit)) {
|
|
703
|
+
lines.push(`- ${formatChainForLlm(chain, metadata)}`);
|
|
704
|
+
}
|
|
705
|
+
if (originChains.length > limit) {
|
|
706
|
+
lines.push(`- +${originChains.length - limit} more origin chains`);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
function normalizeSearchQuery(value) {
|
|
711
|
+
return normalizePath(value).toLowerCase();
|
|
712
|
+
}
|
|
713
|
+
function trimTrailingBlankLines(lines) {
|
|
714
|
+
const nextLines = [...lines];
|
|
715
|
+
while (nextLines.length > 0 && nextLines.at(-1) === "") {
|
|
716
|
+
nextLines.pop();
|
|
717
|
+
}
|
|
718
|
+
return nextLines;
|
|
719
|
+
}
|
|
720
|
+
function compareSourceGroups(left, right) {
|
|
721
|
+
const leftRank = getSourceGroupRank(left.kind);
|
|
722
|
+
const rightRank = getSourceGroupRank(right.kind);
|
|
723
|
+
if (leftRank !== rightRank) {
|
|
724
|
+
return leftRank - rightRank;
|
|
725
|
+
}
|
|
726
|
+
if (right.paths.length !== left.paths.length) {
|
|
727
|
+
return right.paths.length - left.paths.length;
|
|
728
|
+
}
|
|
729
|
+
return left.label.localeCompare(right.label);
|
|
730
|
+
}
|
|
731
|
+
function getSourceGroupRank(kind) {
|
|
732
|
+
switch (kind) {
|
|
733
|
+
case "workspace-package":
|
|
734
|
+
return 0;
|
|
735
|
+
case "file":
|
|
736
|
+
return 1;
|
|
737
|
+
case "external-package":
|
|
738
|
+
return 2;
|
|
739
|
+
default:
|
|
740
|
+
return 3;
|
|
741
|
+
}
|
|
742
|
+
}
|