@kb-labs/quality-entry 2.14.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.
Files changed (74) hide show
  1. package/README.md +120 -0
  2. package/dist/cli/commands/build-order.d.ts +16 -0
  3. package/dist/cli/commands/build-order.js +113 -0
  4. package/dist/cli/commands/build-order.js.map +1 -0
  5. package/dist/cli/commands/check-builds.d.ts +10 -0
  6. package/dist/cli/commands/check-builds.js +93 -0
  7. package/dist/cli/commands/check-builds.js.map +1 -0
  8. package/dist/cli/commands/check-tests.d.ts +10 -0
  9. package/dist/cli/commands/check-tests.js +114 -0
  10. package/dist/cli/commands/check-tests.js.map +1 -0
  11. package/dist/cli/commands/check-types.d.ts +10 -0
  12. package/dist/cli/commands/check-types.js +108 -0
  13. package/dist/cli/commands/check-types.js.map +1 -0
  14. package/dist/cli/commands/cycles.d.ts +17 -0
  15. package/dist/cli/commands/cycles.js +85 -0
  16. package/dist/cli/commands/cycles.js.map +1 -0
  17. package/dist/cli/commands/dead-code.d.ts +10 -0
  18. package/dist/cli/commands/dead-code.js +217 -0
  19. package/dist/cli/commands/dead-code.js.map +1 -0
  20. package/dist/cli/commands/fix-deps.d.ts +28 -0
  21. package/dist/cli/commands/fix-deps.js +389 -0
  22. package/dist/cli/commands/fix-deps.js.map +1 -0
  23. package/dist/cli/commands/flags.d.ts +344 -0
  24. package/dist/cli/commands/flags.js +298 -0
  25. package/dist/cli/commands/flags.js.map +1 -0
  26. package/dist/cli/commands/health.d.ts +10 -0
  27. package/dist/cli/commands/health.js +210 -0
  28. package/dist/cli/commands/health.js.map +1 -0
  29. package/dist/cli/commands/index.d.ts +14 -0
  30. package/dist/cli/commands/index.js +1747 -0
  31. package/dist/cli/commands/index.js.map +1 -0
  32. package/dist/cli/commands/stats.d.ts +10 -0
  33. package/dist/cli/commands/stats.js +282 -0
  34. package/dist/cli/commands/stats.js.map +1 -0
  35. package/dist/cli/commands/visualize.d.ts +26 -0
  36. package/dist/cli/commands/visualize.js +210 -0
  37. package/dist/cli/commands/visualize.js.map +1 -0
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.js +666 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/manifest.d.ts +168 -0
  42. package/dist/manifest.js +666 -0
  43. package/dist/manifest.js.map +1 -0
  44. package/dist/rest/handlers/build-order-handler.d.ts +17 -0
  45. package/dist/rest/handlers/build-order-handler.js +28 -0
  46. package/dist/rest/handlers/build-order-handler.js.map +1 -0
  47. package/dist/rest/handlers/builds-handler.d.ts +12 -0
  48. package/dist/rest/handlers/builds-handler.js +17 -0
  49. package/dist/rest/handlers/builds-handler.js.map +1 -0
  50. package/dist/rest/handlers/cycles-handler.d.ts +13 -0
  51. package/dist/rest/handlers/cycles-handler.js +23 -0
  52. package/dist/rest/handlers/cycles-handler.js.map +1 -0
  53. package/dist/rest/handlers/dependencies-handler.d.ts +14 -0
  54. package/dist/rest/handlers/dependencies-handler.js +19 -0
  55. package/dist/rest/handlers/dependencies-handler.js.map +1 -0
  56. package/dist/rest/handlers/graph-handler.d.ts +30 -0
  57. package/dist/rest/handlers/graph-handler.js +155 -0
  58. package/dist/rest/handlers/graph-handler.js.map +1 -0
  59. package/dist/rest/handlers/health-handler.d.ts +15 -0
  60. package/dist/rest/handlers/health-handler.js +19 -0
  61. package/dist/rest/handlers/health-handler.js.map +1 -0
  62. package/dist/rest/handlers/stale-handler.d.ts +30 -0
  63. package/dist/rest/handlers/stale-handler.js +20 -0
  64. package/dist/rest/handlers/stale-handler.js.map +1 -0
  65. package/dist/rest/handlers/stats-handler.d.ts +16 -0
  66. package/dist/rest/handlers/stats-handler.js +29 -0
  67. package/dist/rest/handlers/stats-handler.js.map +1 -0
  68. package/dist/rest/handlers/tests-handler.d.ts +14 -0
  69. package/dist/rest/handlers/tests-handler.js +19 -0
  70. package/dist/rest/handlers/tests-handler.js.map +1 -0
  71. package/dist/rest/handlers/types-handler.d.ts +12 -0
  72. package/dist/rest/handlers/types-handler.js +17 -0
  73. package/dist/rest/handlers/types-handler.js.map +1 -0
  74. package/package.json +98 -0
@@ -0,0 +1,1747 @@
1
+ import { defineCommand, useLoader } from '@kb-labs/sdk';
2
+ import { buildDependencyGraph, getBuildOrderForPackage, topologicalSort, findCircularDependencies, getReverseDependencies, getImpactAnalysis } from '@kb-labs/quality-core/graph';
3
+ import { CACHE_KEYS } from '@kb-labs/quality-contracts';
4
+ import { checkBuilds } from '@kb-labs/quality-core/builds';
5
+ import { runTests } from '@kb-labs/quality-core/tests';
6
+ import { analyzeTypes } from '@kb-labs/quality-core/types';
7
+ import { listBackups, restoreFromBackup, scanDeadFiles, removeDeadFiles } from '@kb-labs/quality-core/dead-code';
8
+ import fs from 'fs';
9
+ import path3 from 'path';
10
+
11
+ // src/cli/commands/build-order.ts
12
+ var build_order_default = defineCommand({
13
+ id: "quality:build-order",
14
+ description: "Calculate build order using topological sort",
15
+ handler: {
16
+ async execute(ctx, input) {
17
+ const { ui } = ctx;
18
+ const flags = input.flags ?? input;
19
+ const graph = buildDependencyGraph(ctx.cwd);
20
+ let result;
21
+ if (flags.package) {
22
+ result = getBuildOrderForPackage(graph, flags.package);
23
+ } else {
24
+ result = topologicalSort(graph);
25
+ }
26
+ if (result.circular.length > 0) {
27
+ ui?.error?.(
28
+ `Found ${result.circular.length} circular dependencies. Build order cannot be determined.`
29
+ );
30
+ outputCircularDependencies(result.circular, ui);
31
+ return { exitCode: 1, result };
32
+ }
33
+ outputBuildOrder(result, flags, ui);
34
+ return { exitCode: 0, result };
35
+ }
36
+ }
37
+ });
38
+ function outputBuildOrder(result, flags, ui) {
39
+ if (flags.json) {
40
+ ui?.json?.(result);
41
+ return;
42
+ }
43
+ if (flags.script) {
44
+ ui?.write?.("#!/bin/bash");
45
+ ui?.write?.("# Generated build script");
46
+ ui?.write?.("set -e");
47
+ ui?.write?.("");
48
+ for (let i = 0; i < result.layers.length; i++) {
49
+ const layer = result.layers[i];
50
+ if (!layer) {
51
+ continue;
52
+ }
53
+ ui?.write?.(`# Layer ${i + 1} (${layer.length} packages)`);
54
+ for (const pkg of layer) {
55
+ ui?.write?.(`pnpm --filter "${pkg}" run build`);
56
+ }
57
+ ui?.write?.("");
58
+ }
59
+ return;
60
+ }
61
+ const sections = [];
62
+ if (flags.layers) {
63
+ const layerItems = [];
64
+ for (let i = 0; i < result.layers.length; i++) {
65
+ const layer = result.layers[i];
66
+ if (!layer) {
67
+ continue;
68
+ }
69
+ layerItems.push(`Layer ${i + 1}: ${layer.length} packages (can build in parallel)`);
70
+ for (const pkg of layer) {
71
+ layerItems.push(` \u2022 ${pkg}`);
72
+ }
73
+ layerItems.push("");
74
+ }
75
+ sections.push({ header: "Build Layers", items: layerItems });
76
+ } else {
77
+ const orderItems = result.sorted.map((pkg, idx) => `${idx + 1}. ${pkg}`);
78
+ sections.push({ header: "Build Order", items: orderItems });
79
+ }
80
+ sections.push({
81
+ header: "Summary",
82
+ items: [
83
+ `Total packages: ${result.sorted.length}`,
84
+ `Build layers: ${result.layers.length}`,
85
+ `Circular dependencies: ${result.circular.length}`
86
+ ]
87
+ });
88
+ const title = flags.package ? `\u{1F4E6} Build Order for ${flags.package}` : "\u{1F4E6} Monorepo Build Order";
89
+ ui?.success?.("Build order calculated successfully", {
90
+ title,
91
+ sections
92
+ });
93
+ }
94
+ function outputCircularDependencies(cycles, ui) {
95
+ const sections = [];
96
+ for (let i = 0; i < cycles.length; i++) {
97
+ const cycle = cycles[i];
98
+ if (!cycle) {
99
+ continue;
100
+ }
101
+ sections.push({
102
+ header: `Cycle ${i + 1}`,
103
+ items: cycle.map((pkg, idx) => {
104
+ if (idx === cycle.length - 1) {
105
+ const firstPkg = cycle[0];
106
+ return ` ${pkg} \u2192 ${firstPkg ?? "?"} (circular!)`;
107
+ }
108
+ return ` ${pkg} \u2192`;
109
+ })
110
+ });
111
+ }
112
+ ui?.error?.("Circular dependencies detected", {
113
+ title: "\u26A0\uFE0F Circular Dependencies",
114
+ sections
115
+ });
116
+ }
117
+ var check_builds_default = defineCommand({
118
+ id: "quality:check-builds",
119
+ description: "Check build status across monorepo",
120
+ handler: {
121
+ async execute(ctx, input) {
122
+ const { ui, platform } = ctx;
123
+ const flags = input.flags ?? input;
124
+ const cacheKey = `${CACHE_KEYS.BUILDS}:${flags.package || "all"}`;
125
+ if (!flags.refresh) {
126
+ const cached = await platform.cache.get(cacheKey);
127
+ if (cached) {
128
+ outputBuildCheck({ ...cached, cached: true }, flags, ui);
129
+ return { exitCode: cached.failing > 0 ? 1 : 0, result: cached };
130
+ }
131
+ }
132
+ const result = await checkBuilds(ctx.cwd, {
133
+ packageFilter: flags.package,
134
+ timeout: flags.timeout ? Number(flags.timeout) : 3e4
135
+ });
136
+ await platform.cache.set(cacheKey, result, 10 * 60 * 1e3);
137
+ await platform.analytics.track("quality:check-builds", {
138
+ totalPackages: result.totalPackages,
139
+ passing: result.passing,
140
+ failing: result.failing,
141
+ staleBuilds: result.staleBuilds.length,
142
+ duration: result.duration,
143
+ packageSpecific: !!flags.package
144
+ });
145
+ outputBuildCheck({ ...result, cached: false }, flags, ui);
146
+ return {
147
+ exitCode: result.failing > 0 ? 1 : 0,
148
+ result
149
+ };
150
+ }
151
+ }
152
+ });
153
+ function outputBuildCheck(result, flags, ui) {
154
+ if (flags.json) {
155
+ ui?.json?.(result);
156
+ return;
157
+ }
158
+ const sections = [];
159
+ const statusIcon = result.failing === 0 ? "\u2705" : "\u274C";
160
+ const statusText = result.failing === 0 ? "All builds passing" : "Build failures detected";
161
+ const statusItems = [
162
+ `${statusIcon} ${statusText}`,
163
+ `Passing: ${result.passing} package(s)`,
164
+ result.failing > 0 ? `Failing: ${result.failing} package(s)` : null
165
+ ].filter(Boolean);
166
+ sections.push({ header: "Build Status", items: statusItems });
167
+ if (result.failures.length > 0) {
168
+ const failureItems = [];
169
+ for (const failure of result.failures.slice(0, 5)) {
170
+ failureItems.push(`\u2022 ${failure.package}`);
171
+ const firstLine = failure.error.split("\n")[0];
172
+ failureItems.push(` ${firstLine?.substring(0, 80) || "Build failed"}`);
173
+ }
174
+ if (result.failures.length > 5) {
175
+ failureItems.push(`... and ${result.failures.length - 5} more failures`);
176
+ }
177
+ sections.push({ header: "\u274C Failed Packages", items: failureItems });
178
+ }
179
+ if (result.staleBuilds.length > 0) {
180
+ const staleItems = result.staleBuilds.slice(0, 5).map((s) => `\u2022 ${s.package}`);
181
+ if (result.staleBuilds.length > 5) {
182
+ staleItems.push(`... and ${result.staleBuilds.length - 5} more`);
183
+ }
184
+ sections.push({
185
+ header: "\u26A0\uFE0F Stale Builds (dist/ older than src/)",
186
+ items: staleItems
187
+ });
188
+ }
189
+ const summaryItems = [
190
+ `Total packages: ${result.totalPackages}`,
191
+ `Duration: ${(result.duration / 1e3).toFixed(1)}s`,
192
+ result.cached ? "\u{1F4BE} Cached (use --refresh to recheck)" : "\u{1F504} Fresh check"
193
+ ];
194
+ sections.push({ header: "Summary", items: summaryItems });
195
+ const title = result.failing === 0 ? "\u2705 All Builds Passing" : `\u274C ${result.failing} Build Failure(s) Detected`;
196
+ ui?.success?.("Build check completed", {
197
+ title,
198
+ sections
199
+ });
200
+ }
201
+ var check_tests_default = defineCommand({
202
+ id: "quality:check-tests",
203
+ description: "Run tests and track coverage across monorepo",
204
+ handler: {
205
+ async execute(ctx, input) {
206
+ const { ui, platform } = ctx;
207
+ const flags = input.flags ?? input;
208
+ const cacheKey = `${CACHE_KEYS.TESTS}:${flags.package || "all"}`;
209
+ if (!flags.refresh) {
210
+ const cached = await platform.cache.get(cacheKey);
211
+ if (cached) {
212
+ outputTestResults({ ...cached, cached: true }, flags, ui);
213
+ return { exitCode: cached.failing > 0 ? 1 : 0, result: cached };
214
+ }
215
+ }
216
+ const result = await runTests(ctx.cwd, {
217
+ packageFilter: flags.package,
218
+ timeout: flags.timeout ? Number(flags.timeout) : 6e4,
219
+ withCoverage: flags["with-coverage"],
220
+ coverageOnly: flags["coverage-only"]
221
+ });
222
+ await platform.cache.set(cacheKey, result, 5 * 60 * 1e3);
223
+ await platform.analytics.track("quality:check-tests", {
224
+ packageFilter: flags.package || "all",
225
+ totalPackages: result.totalPackages,
226
+ passing: result.passing,
227
+ failing: result.failing,
228
+ withCoverage: flags["with-coverage"],
229
+ cached: false
230
+ });
231
+ outputTestResults({ ...result, cached: false }, flags, ui);
232
+ return {
233
+ exitCode: result.failing > 0 ? 1 : 0,
234
+ result,
235
+ meta: { cached: false }
236
+ };
237
+ }
238
+ }
239
+ });
240
+ function outputTestResults(result, flags, ui) {
241
+ if (flags.json) {
242
+ ui?.json?.(result);
243
+ return;
244
+ }
245
+ const sections = [];
246
+ const statusIcon = result.failing === 0 ? "\u2705" : "\u274C";
247
+ const statusText = result.failing === 0 ? "All tests passing" : "Test failures detected";
248
+ const statusItems = [
249
+ `${statusIcon} ${statusText}`,
250
+ `Total packages: ${result.totalPackages}`,
251
+ `Passing: ${result.passing}`,
252
+ result.failing > 0 ? `Failing: ${result.failing}` : null,
253
+ result.skipped > 0 ? `Skipped: ${result.skipped} (no test script)` : null
254
+ ].filter(Boolean);
255
+ sections.push({ header: "Test Status", items: statusItems });
256
+ if (result.summary.totalTests > 0) {
257
+ const summaryItems2 = [
258
+ `Total tests: ${result.summary.totalTests}`,
259
+ `Passed: ${result.summary.passedTests}`,
260
+ result.summary.failedTests > 0 ? `Failed: ${result.summary.failedTests}` : null
261
+ ].filter(Boolean);
262
+ sections.push({ header: "Test Summary", items: summaryItems2 });
263
+ }
264
+ if (result.failures.length > 0) {
265
+ const failureItems = [];
266
+ for (const failure of result.failures.slice(0, 5)) {
267
+ failureItems.push(`\u274C ${failure.package}`);
268
+ if (failure.failedTests && failure.totalTests) {
269
+ failureItems.push(` ${failure.failedTests}/${failure.totalTests} tests failed`);
270
+ }
271
+ const firstLine = failure.error.split("\n")[0];
272
+ failureItems.push(` ${firstLine?.substring(0, 80) || "Test failed"}`);
273
+ }
274
+ if (result.failures.length > 5) {
275
+ failureItems.push(`... and ${result.failures.length - 5} more failures`);
276
+ }
277
+ sections.push({ header: "Failed Packages", items: failureItems });
278
+ }
279
+ if (result.coverage.packages.length > 0) {
280
+ const coverageItems = [];
281
+ coverageItems.push(`Average coverage: ${result.coverage.avgCoverage.toFixed(1)}%`);
282
+ coverageItems.push("");
283
+ const topPackages = result.coverage.packages.sort((a, b) => b.lines - a.lines).slice(0, 5);
284
+ for (const pkg of topPackages) {
285
+ coverageItems.push(`${pkg.name}`);
286
+ coverageItems.push(
287
+ ` Lines: ${pkg.lines.toFixed(1)}% | Statements: ${pkg.statements.toFixed(1)}% | Functions: ${pkg.functions.toFixed(1)}% | Branches: ${pkg.branches.toFixed(1)}%`
288
+ );
289
+ }
290
+ if (result.coverage.packages.length > 5) {
291
+ coverageItems.push(`... and ${result.coverage.packages.length - 5} more packages`);
292
+ }
293
+ sections.push({ header: "Coverage", items: coverageItems });
294
+ }
295
+ const summaryItems = [
296
+ `Duration: ${(result.duration / 1e3).toFixed(1)}s`,
297
+ result.cached ? "\u{1F4BE} Cached (use --refresh to re-run)" : "\u{1F504} Fresh run"
298
+ ];
299
+ sections.push({ header: "Summary", items: summaryItems });
300
+ const title = result.failing === 0 ? "\u2705 All Tests Passed" : `\u274C ${result.failing} Package(s) with Test Failures`;
301
+ ui?.success?.("Test run completed", {
302
+ title,
303
+ sections
304
+ });
305
+ }
306
+ var check_types_default = defineCommand({
307
+ id: "quality:check-types",
308
+ description: "Analyze TypeScript type safety across monorepo",
309
+ handler: {
310
+ async execute(ctx, input) {
311
+ const { ui, platform } = ctx;
312
+ const flags = input.flags ?? input;
313
+ const cacheKey = `${CACHE_KEYS.TYPE_ANALYSIS}:${flags.package || "all"}`;
314
+ if (!flags.refresh) {
315
+ const cached = await platform.cache.get(cacheKey);
316
+ if (cached) {
317
+ outputTypeAnalysis({ ...cached, cached: true }, flags, ui);
318
+ return { exitCode: cached.totalErrors > 0 ? 1 : 0, result: cached };
319
+ }
320
+ }
321
+ const result = await analyzeTypes(ctx.cwd, {
322
+ packageFilter: flags.package,
323
+ errorsOnly: flags["errors-only"]
324
+ });
325
+ await platform.cache.set(cacheKey, result, 10 * 60 * 1e3);
326
+ await platform.analytics.track("quality:check-types", {
327
+ packageFilter: flags.package || "all",
328
+ totalPackages: result.totalPackages,
329
+ totalErrors: result.totalErrors,
330
+ avgCoverage: result.avgCoverage,
331
+ cached: false
332
+ });
333
+ outputTypeAnalysis({ ...result, cached: false }, flags, ui);
334
+ return {
335
+ exitCode: result.totalErrors > 0 ? 1 : 0,
336
+ result,
337
+ meta: { cached: false }
338
+ };
339
+ }
340
+ }
341
+ });
342
+ function outputTypeAnalysis(result, flags, ui) {
343
+ if (flags.json) {
344
+ ui?.json?.(result);
345
+ return;
346
+ }
347
+ const errorsOnly = flags["errors-only"] || false;
348
+ const sections = [];
349
+ const statusIcon = result.totalErrors === 0 ? "\u2705" : "\u274C";
350
+ const statusText = result.totalErrors === 0 ? "All packages passed type checks" : "Type errors detected";
351
+ const statusItems = [
352
+ `${statusIcon} ${statusText}`,
353
+ `Analyzed: ${result.totalPackages} package(s)`,
354
+ `Errors: ${result.totalErrors}`,
355
+ `Warnings: ${result.totalWarnings}`,
356
+ `Avg Coverage: ${result.avgCoverage.toFixed(1)}%`
357
+ ];
358
+ sections.push({ header: "Type Safety Status", items: statusItems });
359
+ if (result.packagesWithErrors > 0) {
360
+ const errorItems = [];
361
+ const packagesToShow = result.packages.filter((pkg) => !errorsOnly || pkg.errors > 0).slice(0, 10);
362
+ for (const pkg of packagesToShow) {
363
+ const status = pkg.errors > 0 ? "\u274C" : "\u2705";
364
+ errorItems.push(`${status} ${pkg.name}`);
365
+ const details = [
366
+ pkg.errors > 0 ? `${pkg.errors} error(s)` : null,
367
+ pkg.warnings > 0 ? `${pkg.warnings} warning(s)` : null,
368
+ `${pkg.coverage.toFixed(1)}% coverage`,
369
+ pkg.anyCount > 0 ? `${pkg.anyCount} any` : null,
370
+ pkg.tsIgnoreCount > 0 ? `${pkg.tsIgnoreCount} @ts-ignore` : null
371
+ ].filter(Boolean).join(", ");
372
+ errorItems.push(` ${details}`);
373
+ }
374
+ if (result.packages.length > 10) {
375
+ errorItems.push(`... and ${result.packages.length - 10} more packages`);
376
+ }
377
+ sections.push({
378
+ header: `Packages with Type Errors (${result.packagesWithErrors})`,
379
+ items: errorItems
380
+ });
381
+ }
382
+ const excellent = result.packages.filter((p) => p.coverage >= 90).length;
383
+ const good = result.packages.filter((p) => p.coverage >= 70 && p.coverage < 90).length;
384
+ const poor = result.packages.filter((p) => p.coverage < 70).length;
385
+ if (result.packages.length > 0) {
386
+ const coverageItems = [
387
+ `\u2705 Excellent (\u226590%): ${excellent} package(s)`,
388
+ `\u26A0\uFE0F Good (70-90%): ${good} package(s)`,
389
+ poor > 0 ? `\u274C Poor (<70%): ${poor} package(s)` : null
390
+ ].filter(Boolean);
391
+ sections.push({ header: "Type Coverage Distribution", items: coverageItems });
392
+ }
393
+ const summaryItems = [
394
+ `Total packages: ${result.totalPackages}`,
395
+ `Duration: ${(result.duration / 1e3).toFixed(1)}s`,
396
+ result.cached ? "\u{1F4BE} Cached (use --refresh to recheck)" : "\u{1F504} Fresh analysis"
397
+ ];
398
+ sections.push({ header: "Summary", items: summaryItems });
399
+ const title = result.totalErrors === 0 ? "\u2705 All Type Checks Passed" : `\u274C ${result.packagesWithErrors} Package(s) with Type Errors`;
400
+ ui?.success?.("Type analysis completed", {
401
+ title,
402
+ sections
403
+ });
404
+ }
405
+ var cycles_default = defineCommand({
406
+ id: "quality:cycles",
407
+ description: "Detect circular dependencies in monorepo",
408
+ handler: {
409
+ async execute(ctx, input) {
410
+ const { ui } = ctx;
411
+ const flags = input.flags ?? input;
412
+ const graph = buildDependencyGraph(ctx.cwd);
413
+ const cycles = findCircularDependencies(graph);
414
+ outputCycles(cycles, flags, ui);
415
+ return {
416
+ exitCode: cycles.length > 0 ? 1 : 0,
417
+ cycles
418
+ };
419
+ }
420
+ }
421
+ });
422
+ function outputCycles(cycles, flags, ui) {
423
+ if (flags.json) {
424
+ ui?.json?.({ cycles, count: cycles.length });
425
+ return;
426
+ }
427
+ if (cycles.length === 0) {
428
+ ui?.success?.("No circular dependencies found", {
429
+ title: "\u2705 Circular Dependencies Check",
430
+ sections: [
431
+ {
432
+ header: "Result",
433
+ items: ["\u2705 No circular dependencies detected"]
434
+ }
435
+ ]
436
+ });
437
+ return;
438
+ }
439
+ const sections = [];
440
+ sections.push({
441
+ header: "Summary",
442
+ items: [
443
+ `\u26A0\uFE0F Found ${cycles.length} circular dependency chain(s)`,
444
+ "These must be broken to enable clean builds"
445
+ ]
446
+ });
447
+ for (let i = 0; i < cycles.length; i++) {
448
+ const cycle = cycles[i];
449
+ if (!cycle) {
450
+ continue;
451
+ }
452
+ const cycleItems = [];
453
+ for (let j = 0; j < cycle.length; j++) {
454
+ const pkg = cycle[j];
455
+ if (j === cycle.length - 1) {
456
+ cycleItems.push(` ${j + 1}. ${pkg}`);
457
+ const firstPkg = cycle[0];
458
+ cycleItems.push(` ${j + 2}. ${firstPkg ?? "?"} \u2B05 circular!`);
459
+ } else {
460
+ cycleItems.push(` ${j + 1}. ${pkg}`);
461
+ }
462
+ }
463
+ sections.push({
464
+ header: `Cycle ${i + 1} (${cycle.length} packages)`,
465
+ items: cycleItems
466
+ });
467
+ }
468
+ sections.push({
469
+ header: "Recommendations",
470
+ items: [
471
+ "1. Extract shared code into a new package",
472
+ "2. Move one dependency to devDependencies",
473
+ "3. Refactor to remove bidirectional dependencies",
474
+ "4. Use dependency injection patterns"
475
+ ]
476
+ });
477
+ ui?.error?.("Circular dependencies detected", {
478
+ title: `\u26A0\uFE0F Circular Dependencies Detected (${cycles.length})`,
479
+ sections
480
+ });
481
+ }
482
+ var dead_code_default = defineCommand({
483
+ id: "quality:dead-code",
484
+ description: "Detect and optionally remove dead (unreachable) source files",
485
+ handler: {
486
+ async execute(ctx, input) {
487
+ const { ui, platform } = ctx;
488
+ const flags = input.flags ?? input;
489
+ if (flags["list-backups"]) {
490
+ const backups = listBackups(ctx.cwd);
491
+ if (flags.json) {
492
+ ui?.json?.(backups);
493
+ } else {
494
+ outputBackupList(backups, ui);
495
+ }
496
+ return { exitCode: 0, result: backups };
497
+ }
498
+ if (flags.restore) {
499
+ try {
500
+ const result2 = await restoreFromBackup(ctx.cwd, flags.restore);
501
+ if (flags.json) {
502
+ ui?.json?.(result2);
503
+ } else {
504
+ ui?.success?.(`Restored ${result2.restoredFiles} file(s) from backup ${flags.restore}`, {});
505
+ if (result2.restoredExports > 0) {
506
+ ui?.info?.(`Note: ${result2.restoredExports} package.json export(s) were cleaned. You may need to rebuild.`);
507
+ }
508
+ }
509
+ return { exitCode: 0 };
510
+ } catch (err) {
511
+ if (flags.json) {
512
+ ui?.json?.({ error: err.message });
513
+ } else {
514
+ ui?.error?.(err.message);
515
+ }
516
+ return { exitCode: 1 };
517
+ }
518
+ }
519
+ const cacheKey = `${CACHE_KEYS.DEAD_CODE}:${flags.package || "all"}`;
520
+ if (!flags.refresh && !flags["auto-remove"]) {
521
+ const cached = await platform.cache.get(cacheKey);
522
+ if (cached) {
523
+ outputDeadCodeReport({ ...cached, cached: true }, flags, ui);
524
+ return { exitCode: cached.summary.totalDead > 0 ? 1 : 0, result: cached };
525
+ }
526
+ }
527
+ const result = await scanDeadFiles(ctx.cwd, {
528
+ packageFilter: flags.package,
529
+ verbose: flags.verbose
530
+ });
531
+ await platform.cache.set(cacheKey, result, 5 * 60 * 1e3);
532
+ await platform.analytics.track("quality:dead-code", {
533
+ totalPackages: result.summary.totalPackages,
534
+ totalFiles: result.summary.totalFiles,
535
+ totalDead: result.summary.totalDead,
536
+ totalDeadBytes: result.summary.totalDeadBytes,
537
+ duration: result.duration,
538
+ autoRemove: !!flags["auto-remove"],
539
+ dryRun: !!flags["dry-run"]
540
+ });
541
+ if (flags["auto-remove"]) {
542
+ if (result.summary.totalDead === 0) {
543
+ if (flags.json) {
544
+ ui?.json?.({ message: "No dead files found", ...result });
545
+ } else {
546
+ ui?.success?.("No dead files found. Nothing to remove.", {});
547
+ }
548
+ return { exitCode: 0, result };
549
+ }
550
+ const removalResult = await removeDeadFiles(ctx.cwd, result, {
551
+ dryRun: flags["dry-run"]
552
+ });
553
+ if (flags.json) {
554
+ ui?.json?.(removalResult);
555
+ } else {
556
+ outputRemovalReport(removalResult, flags, ui);
557
+ }
558
+ return { exitCode: 0, result: removalResult };
559
+ }
560
+ outputDeadCodeReport({ ...result, cached: false }, flags, ui);
561
+ return {
562
+ exitCode: result.summary.totalDead > 0 ? 1 : 0,
563
+ result
564
+ };
565
+ }
566
+ }
567
+ });
568
+ function outputDeadCodeReport(result, flags, ui) {
569
+ if (flags.json) {
570
+ ui?.json?.(result);
571
+ return;
572
+ }
573
+ const sections = [];
574
+ const packagesWithDead = result.packages.filter((p) => p.deadFiles.length > 0);
575
+ if (packagesWithDead.length > 0) {
576
+ const items = [];
577
+ for (const pkg of packagesWithDead) {
578
+ items.push(`${pkg.packageName} (${pkg.deadFiles.length} dead file${pkg.deadFiles.length === 1 ? "" : "s"})`);
579
+ for (const dead of pkg.deadFiles.slice(0, 5)) {
580
+ const sizeKb = (dead.sizeBytes / 1024).toFixed(1);
581
+ items.push(` ${dead.relativePath} (${sizeKb} KB)`);
582
+ }
583
+ if (pkg.deadFiles.length > 5) {
584
+ items.push(` ... and ${pkg.deadFiles.length - 5} more`);
585
+ }
586
+ }
587
+ sections.push({ header: "Dead Files", items });
588
+ }
589
+ if (flags.verbose) {
590
+ for (const pkg of result.packages) {
591
+ if (pkg.entryPoints.length > 0) {
592
+ const items = pkg.entryPoints.slice(0, 10).map((ep) => ` ${ep}`);
593
+ if (pkg.entryPoints.length > 10) {
594
+ items.push(` ... and ${pkg.entryPoints.length - 10} more`);
595
+ }
596
+ sections.push({ header: `Entry Points: ${pkg.packageName}`, items });
597
+ }
598
+ }
599
+ }
600
+ const allWarnings = result.packages.flatMap((p) => p.warnings);
601
+ if (allWarnings.length > 0) {
602
+ sections.push({
603
+ header: "Warnings",
604
+ items: allWarnings.slice(0, 10)
605
+ });
606
+ }
607
+ const totalDeadKb = (result.summary.totalDeadBytes / 1024).toFixed(1);
608
+ const summaryItems = [
609
+ `Packages scanned: ${result.summary.totalPackages}`,
610
+ `Total source files: ${result.summary.totalFiles}`,
611
+ `Alive files: ${result.summary.totalAlive}`,
612
+ `Dead files: ${result.summary.totalDead}`,
613
+ result.summary.totalDead > 0 ? `Dead code size: ${totalDeadKb} KB` : null,
614
+ result.summary.emptyDirectories.length > 0 ? `Empty directories: ${result.summary.emptyDirectories.length}` : null,
615
+ `Duration: ${(result.duration / 1e3).toFixed(1)}s`,
616
+ result.cached ? "(cached \u2014 use --refresh to rescan)" : null
617
+ ].filter(Boolean);
618
+ sections.push({ header: "Summary", items: summaryItems });
619
+ if (result.summary.totalDead > 0) {
620
+ sections.push({
621
+ header: "Next Steps",
622
+ items: [
623
+ "Run with --auto-remove --dry-run to preview removal",
624
+ "Run with --auto-remove to remove and create backup",
625
+ "Run with --verbose to see entry points"
626
+ ]
627
+ });
628
+ }
629
+ const title = result.summary.totalDead === 0 ? "No dead files found" : `${result.summary.totalDead} dead file(s) found`;
630
+ ui?.success?.("Dead code analysis completed", { title, sections });
631
+ }
632
+ function outputRemovalReport(result, flags, ui) {
633
+ const isDryRun = flags["dry-run"];
634
+ const prefix = isDryRun ? "[DRY RUN] Would" : "Successfully";
635
+ const sections = [];
636
+ const items = [
637
+ `${prefix} remove ${result.filesRemoved} file(s)`,
638
+ `${prefix} free ${(result.bytesRemoved / 1024).toFixed(1)} KB`
639
+ ];
640
+ if (result.emptyDirsRemoved > 0) {
641
+ items.push(`${prefix} remove ${result.emptyDirsRemoved} empty director${result.emptyDirsRemoved === 1 ? "y" : "ies"}`);
642
+ }
643
+ if (result.exportsCleanedUp > 0) {
644
+ items.push(`${prefix} clean ${result.exportsCleanedUp} package.json export(s)`);
645
+ }
646
+ sections.push({ header: isDryRun ? "Preview" : "Removal Complete", items });
647
+ if (!isDryRun) {
648
+ sections.push({
649
+ header: "Backup",
650
+ items: [
651
+ `Backup ID: ${result.backupId}`,
652
+ `To restore: pnpm kb quality:dead-code --restore ${result.backupId}`
653
+ ]
654
+ });
655
+ }
656
+ const fileItems = [];
657
+ for (const file of result.manifest.removedFiles.slice(0, 10)) {
658
+ const sizeKb = (file.sizeBytes / 1024).toFixed(1);
659
+ fileItems.push(`${file.backupPath} (${sizeKb} KB)`);
660
+ }
661
+ if (result.manifest.removedFiles.length > 10) {
662
+ fileItems.push(`... and ${result.manifest.removedFiles.length - 10} more`);
663
+ }
664
+ sections.push({ header: "Files", items: fileItems });
665
+ const title = isDryRun ? `[DRY RUN] Would remove ${result.filesRemoved} file(s)` : `Removed ${result.filesRemoved} file(s)`;
666
+ ui?.success?.(title, { title, sections });
667
+ }
668
+ function outputBackupList(backups, ui) {
669
+ if (backups.length === 0) {
670
+ ui?.info?.("No backups found.");
671
+ return;
672
+ }
673
+ const sections = [];
674
+ for (const backup of backups) {
675
+ const date = new Date(backup.createdAt).toLocaleString();
676
+ const items = [
677
+ `Date: ${date}`,
678
+ `Git: ${backup.gitBranch} @ ${backup.gitSha.slice(0, 8)}`,
679
+ `Files: ${backup.totalFilesRemoved}`,
680
+ `Size: ${(backup.totalBytesRemoved / 1024).toFixed(1)} KB`,
681
+ `Restore: pnpm kb quality:dead-code --restore ${backup.id}`
682
+ ];
683
+ sections.push({ header: `Backup: ${backup.id}`, items });
684
+ }
685
+ ui?.success?.(`${backups.length} backup(s) found`, {
686
+ title: "Dead Code Backups",
687
+ sections
688
+ });
689
+ }
690
+ var fix_deps_default = defineCommand({
691
+ id: "quality:fix-deps",
692
+ description: "Auto-fix dependency issues",
693
+ handler: {
694
+ async execute(ctx, input) {
695
+ const { ui, platform } = ctx;
696
+ const flags = input.flags ?? input;
697
+ const dryRun = flags["dry-run"] ?? false;
698
+ const removeUnused = flags["remove-unused"] || flags.all;
699
+ const addMissing = flags["add-missing"] || flags.all;
700
+ const alignVersions = flags["align-versions"] || flags.all;
701
+ const showStats = flags.stats;
702
+ if (showStats) {
703
+ const stats = await getDependencyStats(ctx.cwd);
704
+ outputStats(stats, flags, ui);
705
+ return { exitCode: 0 };
706
+ }
707
+ if (!removeUnused && !addMissing && !alignVersions) {
708
+ ui?.error?.("No fix options specified. Use --remove-unused, --add-missing, --align-versions, or --all");
709
+ return { exitCode: 1 };
710
+ }
711
+ const loader = useLoader("Scanning packages...");
712
+ loader.start();
713
+ const result = {
714
+ packagesScanned: 0,
715
+ removedDeps: [],
716
+ addedDeps: [],
717
+ alignedDeps: [],
718
+ dryRun
719
+ };
720
+ const packages = findPackages(ctx.cwd);
721
+ result.packagesScanned = packages.length;
722
+ loader.update({ text: `Scanned ${packages.length} packages` });
723
+ if (alignVersions) {
724
+ loader.update({ text: "Analyzing duplicate dependencies..." });
725
+ const aligned = await alignDuplicateVersions(packages, dryRun);
726
+ result.alignedDeps = aligned;
727
+ }
728
+ if (addMissing) {
729
+ loader.update({ text: "Checking for missing workspace dependencies..." });
730
+ const added = await addMissingWorkspaceDeps(packages, dryRun);
731
+ result.addedDeps = added;
732
+ }
733
+ if (removeUnused) {
734
+ loader.update({ text: "Checking for unused dependencies..." });
735
+ const removed = await removeUnusedDeps(packages, dryRun);
736
+ result.removedDeps = removed;
737
+ }
738
+ loader.succeed("Dependency analysis completed");
739
+ await platform.analytics.track("quality:fix-deps", {
740
+ dryRun,
741
+ packagesScanned: result.packagesScanned,
742
+ removedCount: result.removedDeps.length,
743
+ addedCount: result.addedDeps.length,
744
+ alignedCount: result.alignedDeps.length
745
+ });
746
+ outputResults(result, flags, ui);
747
+ return {
748
+ exitCode: 0,
749
+ result
750
+ };
751
+ }
752
+ }
753
+ });
754
+ function findPackages(rootDir) {
755
+ const packages = [];
756
+ if (!fs.existsSync(rootDir)) {
757
+ return packages;
758
+ }
759
+ const entries = fs.readdirSync(rootDir, { withFileTypes: true });
760
+ for (const entry of entries) {
761
+ if (!entry.isDirectory() || !entry.name.startsWith("kb-labs-")) {
762
+ continue;
763
+ }
764
+ const repoPath = path3.join(rootDir, entry.name);
765
+ const packagesDir = path3.join(repoPath, "packages");
766
+ if (!fs.existsSync(packagesDir)) {
767
+ continue;
768
+ }
769
+ const packageDirs = fs.readdirSync(packagesDir, { withFileTypes: true });
770
+ for (const pkgDir of packageDirs) {
771
+ if (!pkgDir.isDirectory()) {
772
+ continue;
773
+ }
774
+ const packageJsonPath = path3.join(packagesDir, pkgDir.name, "package.json");
775
+ if (fs.existsSync(packageJsonPath)) {
776
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
777
+ packages.push({
778
+ name: packageJson.name || pkgDir.name,
779
+ path: packageJsonPath,
780
+ json: packageJson
781
+ });
782
+ }
783
+ }
784
+ }
785
+ return packages;
786
+ }
787
+ async function getDependencyStats(rootDir) {
788
+ const packages = findPackages(rootDir);
789
+ const allDeps = /* @__PURE__ */ new Map();
790
+ for (const pkg of packages) {
791
+ const deps = {
792
+ ...pkg.json.dependencies,
793
+ ...pkg.json.devDependencies
794
+ };
795
+ for (const [dep, version] of Object.entries(deps)) {
796
+ if (!allDeps.has(dep)) {
797
+ allDeps.set(dep, /* @__PURE__ */ new Set());
798
+ }
799
+ allDeps.get(dep).add(version);
800
+ }
801
+ }
802
+ const duplicates = Array.from(allDeps.entries()).filter(([_, versions]) => versions.size > 1).filter(([dep]) => !dep.startsWith("@kb-labs/"));
803
+ const topUsed = Array.from(allDeps.entries()).map(([name, versions]) => ({ name, count: versions.size > 1 ? versions.size : 1 })).sort((a, b) => b.count - a.count).slice(0, 10);
804
+ return {
805
+ totalPackages: packages.length,
806
+ totalDeps: allDeps.size,
807
+ duplicates: duplicates.length,
808
+ topUsed
809
+ };
810
+ }
811
+ async function alignDuplicateVersions(packages, dryRun) {
812
+ const aligned = [];
813
+ const depVersions = /* @__PURE__ */ new Map();
814
+ for (const pkg of packages) {
815
+ const deps = {
816
+ ...pkg.json.dependencies,
817
+ ...pkg.json.devDependencies
818
+ };
819
+ for (const [dep, version] of Object.entries(deps)) {
820
+ if (dep.startsWith("@kb-labs/")) {
821
+ continue;
822
+ }
823
+ if (!depVersions.has(dep)) {
824
+ depVersions.set(dep, /* @__PURE__ */ new Map());
825
+ }
826
+ const versions = depVersions.get(dep);
827
+ if (!versions.has(version)) {
828
+ versions.set(version, /* @__PURE__ */ new Set());
829
+ }
830
+ versions.get(version).add(pkg.name);
831
+ }
832
+ }
833
+ for (const [dep, versions] of depVersions.entries()) {
834
+ if (versions.size <= 1) {
835
+ continue;
836
+ }
837
+ const versionCounts = Array.from(versions.entries()).map(([ver, pkgs]) => ({
838
+ version: ver,
839
+ count: pkgs.size,
840
+ packages: Array.from(pkgs)
841
+ }));
842
+ versionCounts.sort((a, b) => b.count - a.count);
843
+ const targetVersion = versionCounts[0]?.version;
844
+ if (!targetVersion) {
845
+ continue;
846
+ }
847
+ for (const { version, packages: pkgNames } of versionCounts.slice(1)) {
848
+ aligned.push({
849
+ dep,
850
+ from: version,
851
+ to: targetVersion,
852
+ packages: pkgNames
853
+ });
854
+ if (!dryRun) {
855
+ for (const pkgName of pkgNames) {
856
+ const pkg = packages.find((p) => p.name === pkgName);
857
+ if (!pkg) {
858
+ continue;
859
+ }
860
+ if (pkg.json.dependencies?.[dep]) {
861
+ pkg.json.dependencies[dep] = targetVersion;
862
+ }
863
+ if (pkg.json.devDependencies?.[dep]) {
864
+ pkg.json.devDependencies[dep] = targetVersion;
865
+ }
866
+ fs.writeFileSync(pkg.path, JSON.stringify(pkg.json, null, 2) + "\n");
867
+ }
868
+ }
869
+ }
870
+ }
871
+ return aligned;
872
+ }
873
+ async function addMissingWorkspaceDeps(packages, dryRun) {
874
+ const added = [];
875
+ const workspacePackages = /* @__PURE__ */ new Map();
876
+ for (const pkg of packages) {
877
+ if (pkg.name.startsWith("@kb-labs/")) {
878
+ workspacePackages.set(pkg.name, "workspace:*");
879
+ }
880
+ }
881
+ for (const pkg of packages) {
882
+ const packageDir = path3.dirname(pkg.path);
883
+ const srcDir = path3.join(packageDir, "src");
884
+ if (!fs.existsSync(srcDir)) {
885
+ continue;
886
+ }
887
+ const imports = scanImports(srcDir);
888
+ const currentDeps = {
889
+ ...pkg.json.dependencies,
890
+ ...pkg.json.devDependencies
891
+ };
892
+ for (const imp of imports) {
893
+ if (imp.startsWith("@kb-labs/") && workspacePackages.has(imp) && !currentDeps[imp]) {
894
+ added.push({
895
+ package: pkg.name,
896
+ dep: imp,
897
+ version: "workspace:*"
898
+ });
899
+ if (!dryRun) {
900
+ if (!pkg.json.dependencies) {
901
+ pkg.json.dependencies = {};
902
+ }
903
+ pkg.json.dependencies[imp] = "workspace:*";
904
+ fs.writeFileSync(pkg.path, JSON.stringify(pkg.json, null, 2) + "\n");
905
+ }
906
+ }
907
+ }
908
+ }
909
+ return added;
910
+ }
911
+ async function removeUnusedDeps(packages, dryRun) {
912
+ const removed = [];
913
+ for (const pkg of packages) {
914
+ const packageDir = path3.dirname(pkg.path);
915
+ const srcDir = path3.join(packageDir, "src");
916
+ if (!fs.existsSync(srcDir)) {
917
+ continue;
918
+ }
919
+ const imports = new Set(scanImports(srcDir));
920
+ const deps = {
921
+ ...pkg.json.dependencies,
922
+ ...pkg.json.devDependencies
923
+ };
924
+ for (const dep of Object.keys(deps)) {
925
+ if (isProtectedDep(dep)) {
926
+ continue;
927
+ }
928
+ if (!imports.has(dep)) {
929
+ removed.push({
930
+ package: pkg.name,
931
+ dep
932
+ });
933
+ if (!dryRun) {
934
+ delete pkg.json.dependencies?.[dep];
935
+ delete pkg.json.devDependencies?.[dep];
936
+ fs.writeFileSync(pkg.path, JSON.stringify(pkg.json, null, 2) + "\n");
937
+ }
938
+ }
939
+ }
940
+ }
941
+ return removed;
942
+ }
943
+ function scanImports(dir) {
944
+ const imports = [];
945
+ function walk(currentDir) {
946
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
947
+ for (const entry of entries) {
948
+ const fullPath = path3.join(currentDir, entry.name);
949
+ if (entry.isDirectory()) {
950
+ walk(fullPath);
951
+ } else if (entry.isFile() && /\.(ts|tsx|js|jsx)$/.test(entry.name)) {
952
+ const content = fs.readFileSync(fullPath, "utf-8");
953
+ const importRegex = /(?:import|require)\s*\(?['"]([^'"]+)['"]\)?/g;
954
+ let match;
955
+ while ((match = importRegex.exec(content)) !== null) {
956
+ const imp = match[1];
957
+ if (!imp) {
958
+ continue;
959
+ }
960
+ const pkgName = imp.startsWith("@") ? imp.split("/").slice(0, 2).join("/") : imp.split("/")[0];
961
+ if (pkgName) {
962
+ imports.push(pkgName);
963
+ }
964
+ }
965
+ }
966
+ }
967
+ }
968
+ walk(dir);
969
+ return Array.from(new Set(imports));
970
+ }
971
+ function isProtectedDep(dep) {
972
+ const protectedDeps = [
973
+ "typescript",
974
+ "tsup",
975
+ "esbuild",
976
+ "vite",
977
+ "rollup",
978
+ "rimraf",
979
+ "vitest",
980
+ "jest",
981
+ "playwright",
982
+ "eslint",
983
+ "prettier"
984
+ ];
985
+ return protectedDeps.some((p) => dep === p || dep.startsWith(`${p}-`) || dep.startsWith(`@${p}/`));
986
+ }
987
+ function outputStats(stats, flags, ui) {
988
+ if (flags.json) {
989
+ ui?.json?.(stats);
990
+ return;
991
+ }
992
+ const sections = [];
993
+ sections.push({
994
+ header: "Overview",
995
+ items: [
996
+ `Total Packages: ${stats.totalPackages}`,
997
+ `Total Dependencies: ${stats.totalDeps}`,
998
+ `Duplicate Versions: ${stats.duplicates}`
999
+ ]
1000
+ });
1001
+ if (stats.topUsed.length > 0) {
1002
+ sections.push({
1003
+ header: "Top 10 Most Used",
1004
+ items: stats.topUsed.map((d) => `${d.name} (${d.count} packages)`)
1005
+ });
1006
+ }
1007
+ ui?.success?.("Dependency statistics", {
1008
+ title: "\u{1F4CA} Dependency Statistics",
1009
+ sections
1010
+ });
1011
+ }
1012
+ function outputResults(result, flags, ui) {
1013
+ if (flags.json) {
1014
+ ui?.json?.(result);
1015
+ return;
1016
+ }
1017
+ const sections = [];
1018
+ const summaryItems = [
1019
+ `Packages Scanned: ${result.packagesScanned}`,
1020
+ `Mode: ${result.dryRun ? "\u{1F50D} Dry Run (no changes applied)" : "\u2705 Changes Applied"}`
1021
+ ];
1022
+ sections.push({ header: "Summary", items: summaryItems });
1023
+ if (result.alignedDeps.length > 0) {
1024
+ const alignedItems = result.alignedDeps.map(
1025
+ (a) => `${a.dep}: ${a.from} \u2192 ${a.to} (${a.packages.length} packages)`
1026
+ );
1027
+ sections.push({ header: "Aligned Versions", items: alignedItems.slice(0, 10) });
1028
+ if (result.alignedDeps.length > 10) {
1029
+ const lastSection = sections[sections.length - 1];
1030
+ if (lastSection) {
1031
+ lastSection.items.push(`... and ${result.alignedDeps.length - 10} more`);
1032
+ }
1033
+ }
1034
+ }
1035
+ if (result.addedDeps.length > 0) {
1036
+ const addedItems = result.addedDeps.map((a) => `${a.package}: +${a.dep}`);
1037
+ sections.push({ header: "Added Dependencies", items: addedItems.slice(0, 10) });
1038
+ if (result.addedDeps.length > 10) {
1039
+ const lastSection = sections[sections.length - 1];
1040
+ if (lastSection) {
1041
+ lastSection.items.push(`... and ${result.addedDeps.length - 10} more`);
1042
+ }
1043
+ }
1044
+ }
1045
+ if (result.removedDeps.length > 0) {
1046
+ const removedItems = result.removedDeps.map((r) => `${r.package}: -${r.dep}`);
1047
+ sections.push({ header: "Removed Dependencies", items: removedItems.slice(0, 10) });
1048
+ if (result.removedDeps.length > 10) {
1049
+ const lastSection = sections[sections.length - 1];
1050
+ if (lastSection) {
1051
+ lastSection.items.push(`... and ${result.removedDeps.length - 10} more`);
1052
+ }
1053
+ }
1054
+ }
1055
+ if (result.alignedDeps.length === 0 && result.addedDeps.length === 0 && result.removedDeps.length === 0) {
1056
+ sections.push({ header: "Result", items: ["\u2705 No issues found!"] });
1057
+ }
1058
+ if (result.dryRun && (result.alignedDeps.length > 0 || result.addedDeps.length > 0 || result.removedDeps.length > 0)) {
1059
+ sections.push({
1060
+ header: "Next Steps",
1061
+ items: ["Remove --dry-run flag to apply changes", "Run `pnpm install` after applying changes"]
1062
+ });
1063
+ }
1064
+ const title = result.dryRun ? "\u{1F50D} Dependency Fix Preview" : "\u2705 Dependency Fix Complete";
1065
+ ui?.success?.("Dependency fix completed", {
1066
+ title,
1067
+ sections
1068
+ });
1069
+ }
1070
+ var health_default = defineCommand({
1071
+ id: "quality:health",
1072
+ description: "Check monorepo health score",
1073
+ handler: {
1074
+ async execute(ctx, input) {
1075
+ const { ui, platform } = ctx;
1076
+ const flags = input.flags ?? input;
1077
+ const health = await calculateHealth(ctx.cwd, flags.package);
1078
+ await platform.cache.set(CACHE_KEYS.HEALTH, health, 5 * 60 * 1e3);
1079
+ await platform.analytics.track("quality:health", {
1080
+ score: health.score,
1081
+ grade: health.grade,
1082
+ issueCount: health.issues.length,
1083
+ packageSpecific: !!flags.package
1084
+ });
1085
+ outputHealth(health, flags, ui);
1086
+ return {
1087
+ exitCode: health.score < 60 ? 1 : 0,
1088
+ result: health
1089
+ };
1090
+ }
1091
+ }
1092
+ });
1093
+ function findPackages2(rootDir, specificPackage) {
1094
+ const packages = [];
1095
+ if (!fs.existsSync(rootDir)) {
1096
+ return packages;
1097
+ }
1098
+ const entries = fs.readdirSync(rootDir, { withFileTypes: true });
1099
+ for (const entry of entries) {
1100
+ if (!entry.isDirectory() || !entry.name.startsWith("kb-labs-")) {
1101
+ continue;
1102
+ }
1103
+ const repoPath = path3.join(rootDir, entry.name);
1104
+ const packagesDir = path3.join(repoPath, "packages");
1105
+ if (!fs.existsSync(packagesDir)) {
1106
+ continue;
1107
+ }
1108
+ const packageDirs = fs.readdirSync(packagesDir, { withFileTypes: true });
1109
+ for (const pkgDir of packageDirs) {
1110
+ if (!pkgDir.isDirectory()) {
1111
+ continue;
1112
+ }
1113
+ const packageJsonPath = path3.join(packagesDir, pkgDir.name, "package.json");
1114
+ if (fs.existsSync(packageJsonPath)) {
1115
+ if (specificPackage) {
1116
+ const pkgJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
1117
+ if (pkgJson.name === specificPackage || pkgDir.name === specificPackage) {
1118
+ packages.push(packageJsonPath);
1119
+ }
1120
+ } else {
1121
+ packages.push(packageJsonPath);
1122
+ }
1123
+ }
1124
+ }
1125
+ }
1126
+ return packages;
1127
+ }
1128
+ async function calculateHealth(rootDir, specificPackage) {
1129
+ const packages = findPackages2(rootDir, specificPackage);
1130
+ let score = 100;
1131
+ const issues = [];
1132
+ const allDeps = /* @__PURE__ */ new Map();
1133
+ for (const packagePath of packages) {
1134
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
1135
+ const deps = {
1136
+ ...packageJson.dependencies,
1137
+ ...packageJson.devDependencies
1138
+ };
1139
+ for (const [dep, version] of Object.entries(deps)) {
1140
+ if (!allDeps.has(dep)) {
1141
+ allDeps.set(dep, /* @__PURE__ */ new Set());
1142
+ }
1143
+ allDeps.get(dep).add(version);
1144
+ }
1145
+ }
1146
+ const duplicates = Array.from(allDeps.entries()).filter(([_, versions]) => versions.size > 1).filter(([dep]) => !dep.startsWith("@kb-labs/"));
1147
+ if (duplicates.length > 0) {
1148
+ const penalty = Math.min(20, duplicates.length * 2);
1149
+ score -= penalty;
1150
+ issues.push({
1151
+ type: "duplicates",
1152
+ severity: "warning",
1153
+ message: `${duplicates.length} duplicate dependencies (-${penalty})`
1154
+ });
1155
+ }
1156
+ let missingReadmes = 0;
1157
+ for (const packagePath of packages) {
1158
+ const packageDir = path3.dirname(packagePath);
1159
+ const readmePath = path3.join(packageDir, "README.md");
1160
+ if (!fs.existsSync(readmePath)) {
1161
+ missingReadmes++;
1162
+ }
1163
+ }
1164
+ if (missingReadmes > 0) {
1165
+ const penalty = Math.min(15, missingReadmes);
1166
+ score -= penalty;
1167
+ issues.push({
1168
+ type: "missing-docs",
1169
+ severity: "warning",
1170
+ message: `${missingReadmes} packages missing README (-${penalty})`
1171
+ });
1172
+ }
1173
+ let namingIssues = 0;
1174
+ for (const packagePath of packages) {
1175
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
1176
+ const packageName = packageJson.name;
1177
+ if (!packageName || !packageName.startsWith("@kb-labs/")) {
1178
+ namingIssues++;
1179
+ }
1180
+ }
1181
+ if (namingIssues > 0) {
1182
+ const penalty = Math.min(10, namingIssues * 2);
1183
+ score -= penalty;
1184
+ issues.push({
1185
+ type: "naming",
1186
+ severity: "critical",
1187
+ message: `${namingIssues} packages with naming issues (-${penalty})`
1188
+ });
1189
+ }
1190
+ let missingTypes = 0;
1191
+ for (const packagePath of packages) {
1192
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
1193
+ if (!packageJson.types && !packageJson.typings) {
1194
+ missingTypes++;
1195
+ }
1196
+ }
1197
+ if (missingTypes > 0) {
1198
+ const penalty = Math.min(15, missingTypes);
1199
+ score -= penalty;
1200
+ issues.push({
1201
+ type: "missing-types",
1202
+ severity: "warning",
1203
+ message: `${missingTypes} packages missing types field (-${penalty})`
1204
+ });
1205
+ }
1206
+ let grade;
1207
+ if (score >= 90) {
1208
+ grade = "A";
1209
+ } else if (score >= 80) {
1210
+ grade = "B";
1211
+ } else if (score >= 70) {
1212
+ grade = "C";
1213
+ } else if (score >= 60) {
1214
+ grade = "D";
1215
+ } else {
1216
+ grade = "F";
1217
+ }
1218
+ return {
1219
+ score: Math.max(0, score),
1220
+ grade,
1221
+ issues
1222
+ };
1223
+ }
1224
+ function outputHealth(health, flags, ui) {
1225
+ if (flags.json) {
1226
+ ui?.json?.(health);
1227
+ return;
1228
+ }
1229
+ const sections = [];
1230
+ const scoreItems = [
1231
+ `Score: ${health.score}/100`,
1232
+ `Grade: ${health.grade}`,
1233
+ `Status: ${health.score >= 80 ? "\u2705 Healthy" : health.score >= 60 ? "\u26A0\uFE0F Needs Attention" : "\u274C Critical"}`
1234
+ ];
1235
+ sections.push({ header: "Health Score", items: scoreItems });
1236
+ if (health.issues.length > 0) {
1237
+ const issueItems = health.issues.map((issue) => {
1238
+ const icon = issue.severity === "critical" ? "\u274C" : issue.severity === "warning" ? "\u26A0\uFE0F" : "\u2139\uFE0F";
1239
+ return `${icon} ${issue.message}`;
1240
+ });
1241
+ if (flags.detailed) {
1242
+ sections.push({ header: "Issues (Detailed)", items: issueItems });
1243
+ } else {
1244
+ sections.push({ header: "Issues", items: issueItems });
1245
+ }
1246
+ } else {
1247
+ sections.push({ header: "Issues", items: ["\u2705 No issues found!"] });
1248
+ }
1249
+ if (health.score < 90) {
1250
+ const recommendations = [];
1251
+ if (health.issues.some((i) => i.type === "duplicates")) {
1252
+ recommendations.push("Run `kb quality:fix-deps --align-versions` to fix duplicates");
1253
+ }
1254
+ if (health.issues.some((i) => i.type === "missing-docs")) {
1255
+ recommendations.push("Add README.md files to packages");
1256
+ }
1257
+ if (health.issues.some((i) => i.type === "missing-types")) {
1258
+ recommendations.push('Add "types" field to package.json or enable dts generation');
1259
+ }
1260
+ if (recommendations.length > 0) {
1261
+ sections.push({ header: "Recommendations", items: recommendations });
1262
+ }
1263
+ }
1264
+ const title = health.score >= 80 ? "\u{1F49A} Monorepo Health Check - Healthy" : health.score >= 60 ? "\u{1F49B} Monorepo Health Check - Needs Attention" : "\u2764\uFE0F Monorepo Health Check - Critical";
1265
+ ui?.success?.("Health check completed", {
1266
+ title,
1267
+ sections
1268
+ });
1269
+ }
1270
+ var stats_default = defineCommand({
1271
+ id: "quality:stats",
1272
+ description: "Show monorepo statistics and health score",
1273
+ handler: {
1274
+ async execute(ctx, input) {
1275
+ const { ui, platform } = ctx;
1276
+ const flags = input.flags ?? input;
1277
+ if (!flags.refresh) {
1278
+ const cached = await platform.cache.get(CACHE_KEYS.STATS);
1279
+ if (cached) {
1280
+ outputStats2(cached, flags, ui);
1281
+ return { exitCode: 0 };
1282
+ }
1283
+ }
1284
+ const stats = await collectStats(ctx.cwd);
1285
+ if (flags.health) {
1286
+ stats.health = calculateHealthScore(stats);
1287
+ }
1288
+ await platform.cache.set(CACHE_KEYS.STATS, stats, 5 * 60 * 1e3);
1289
+ await platform.analytics.track("quality:stats", {
1290
+ packages: stats.overview.totalPackages,
1291
+ repositories: stats.overview.totalRepositories,
1292
+ withHealth: flags.health ?? false
1293
+ });
1294
+ outputStats2(stats, flags, ui);
1295
+ return { exitCode: 0 };
1296
+ }
1297
+ }
1298
+ });
1299
+ function findPackages3(rootDir) {
1300
+ const packages = [];
1301
+ if (!fs.existsSync(rootDir)) {
1302
+ return packages;
1303
+ }
1304
+ const entries = fs.readdirSync(rootDir, { withFileTypes: true });
1305
+ for (const entry of entries) {
1306
+ if (!entry.isDirectory() || !entry.name.startsWith("kb-labs-")) {
1307
+ continue;
1308
+ }
1309
+ const repoPath = path3.join(rootDir, entry.name);
1310
+ const packagesDir = path3.join(repoPath, "packages");
1311
+ if (!fs.existsSync(packagesDir)) {
1312
+ continue;
1313
+ }
1314
+ const packageDirs = fs.readdirSync(packagesDir, { withFileTypes: true });
1315
+ for (const pkgDir of packageDirs) {
1316
+ if (!pkgDir.isDirectory()) {
1317
+ continue;
1318
+ }
1319
+ const packageJsonPath = path3.join(packagesDir, pkgDir.name, "package.json");
1320
+ if (fs.existsSync(packageJsonPath)) {
1321
+ packages.push(packageJsonPath);
1322
+ }
1323
+ }
1324
+ }
1325
+ return packages;
1326
+ }
1327
+ function calculateSize(packageDir) {
1328
+ const srcDir = path3.join(packageDir, "src");
1329
+ if (!fs.existsSync(srcDir)) {
1330
+ return { files: 0, lines: 0, bytes: 0 };
1331
+ }
1332
+ let files = 0;
1333
+ let lines = 0;
1334
+ let bytes = 0;
1335
+ function walk(dir) {
1336
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
1337
+ for (const entry of entries) {
1338
+ const fullPath = path3.join(dir, entry.name);
1339
+ if (entry.isDirectory()) {
1340
+ walk(fullPath);
1341
+ } else if (entry.isFile() && /\.(ts|tsx|js|jsx)$/.test(entry.name)) {
1342
+ files++;
1343
+ const content = fs.readFileSync(fullPath, "utf-8");
1344
+ lines += content.split("\n").length;
1345
+ bytes += Buffer.byteLength(content, "utf-8");
1346
+ }
1347
+ }
1348
+ }
1349
+ walk(srcDir);
1350
+ return { files, lines, bytes };
1351
+ }
1352
+ async function collectStats(rootDir) {
1353
+ const packages = findPackages3(rootDir);
1354
+ const stats = {
1355
+ overview: {
1356
+ totalPackages: 0,
1357
+ totalRepositories: 0,
1358
+ totalFiles: 0,
1359
+ totalLines: 0,
1360
+ totalBytes: 0
1361
+ },
1362
+ byRepository: {},
1363
+ dependencies: {
1364
+ total: 0,
1365
+ workspace: 0,
1366
+ external: 0,
1367
+ duplicates: 0,
1368
+ topUsed: []
1369
+ },
1370
+ health: {
1371
+ score: 0,
1372
+ grade: "F",
1373
+ issues: []
1374
+ },
1375
+ largestPackages: []
1376
+ };
1377
+ const repos = /* @__PURE__ */ new Set();
1378
+ const allDeps = /* @__PURE__ */ new Map();
1379
+ for (const packagePath of packages) {
1380
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
1381
+ const packageName = packageJson.name;
1382
+ if (!packageName || !packageName.startsWith("@kb-labs/")) {
1383
+ continue;
1384
+ }
1385
+ stats.overview.totalPackages++;
1386
+ const packageDir = path3.dirname(packagePath);
1387
+ const repoName = path3.basename(path3.dirname(path3.dirname(packageDir)));
1388
+ repos.add(repoName);
1389
+ const size = calculateSize(packageDir);
1390
+ stats.overview.totalFiles += size.files;
1391
+ stats.overview.totalLines += size.lines;
1392
+ stats.overview.totalBytes += size.bytes;
1393
+ if (!stats.byRepository[repoName]) {
1394
+ stats.byRepository[repoName] = {
1395
+ name: repoName,
1396
+ packages: 0,
1397
+ files: 0,
1398
+ lines: 0,
1399
+ bytes: 0
1400
+ };
1401
+ }
1402
+ stats.byRepository[repoName].packages++;
1403
+ stats.byRepository[repoName].files += size.files;
1404
+ stats.byRepository[repoName].lines += size.lines;
1405
+ stats.byRepository[repoName].bytes += size.bytes;
1406
+ const deps = {
1407
+ ...packageJson.dependencies,
1408
+ ...packageJson.devDependencies
1409
+ };
1410
+ for (const dep of Object.keys(deps)) {
1411
+ stats.dependencies.total++;
1412
+ if (dep.startsWith("@kb-labs/")) {
1413
+ stats.dependencies.workspace++;
1414
+ } else {
1415
+ stats.dependencies.external++;
1416
+ }
1417
+ allDeps.set(dep, (allDeps.get(dep) || 0) + 1);
1418
+ }
1419
+ stats.largestPackages.push({
1420
+ name: packageName,
1421
+ repository: repoName,
1422
+ files: size.files,
1423
+ lines: size.lines,
1424
+ bytes: size.bytes
1425
+ });
1426
+ }
1427
+ stats.overview.totalRepositories = repos.size;
1428
+ stats.dependencies.duplicates = Array.from(allDeps.values()).filter((count) => count > 1).length;
1429
+ stats.dependencies.topUsed = Array.from(allDeps.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([name, count]) => ({ name, count }));
1430
+ stats.largestPackages.sort((a, b) => b.lines - a.lines);
1431
+ stats.largestPackages = stats.largestPackages.slice(0, 10);
1432
+ return stats;
1433
+ }
1434
+ function calculateHealthScore(stats) {
1435
+ let score = 100;
1436
+ const issues = [];
1437
+ if (stats.dependencies.duplicates > 0) {
1438
+ const penalty = Math.min(20, stats.dependencies.duplicates * 2);
1439
+ score -= penalty;
1440
+ issues.push({
1441
+ type: "duplicates",
1442
+ severity: "warning",
1443
+ message: `${stats.dependencies.duplicates} duplicate dependencies (-${penalty})`
1444
+ });
1445
+ }
1446
+ let grade;
1447
+ if (score >= 90) {
1448
+ grade = "A";
1449
+ } else if (score >= 80) {
1450
+ grade = "B";
1451
+ } else if (score >= 70) {
1452
+ grade = "C";
1453
+ } else if (score >= 60) {
1454
+ grade = "D";
1455
+ } else {
1456
+ grade = "F";
1457
+ }
1458
+ return {
1459
+ score,
1460
+ grade,
1461
+ issues
1462
+ };
1463
+ }
1464
+ function outputStats2(stats, flags, ui) {
1465
+ if (flags.json) {
1466
+ ui?.json?.(stats);
1467
+ return;
1468
+ }
1469
+ if (flags.md) {
1470
+ outputMarkdown(stats);
1471
+ return;
1472
+ }
1473
+ const overviewItems = [
1474
+ `Packages: ${stats.overview.totalPackages}`,
1475
+ `Repositories: ${stats.overview.totalRepositories}`,
1476
+ `Lines of Code: ${stats.overview.totalLines.toLocaleString()}`,
1477
+ `Total Size: ${formatBytes(stats.overview.totalBytes)}`
1478
+ ];
1479
+ const sections = [
1480
+ { header: "Overview", items: overviewItems }
1481
+ ];
1482
+ if (flags.health && stats.health) {
1483
+ const healthItems = [
1484
+ `Score: ${stats.health.score}/100 (Grade ${stats.health.grade})`
1485
+ ];
1486
+ if (stats.health.issues.length > 0) {
1487
+ healthItems.push("");
1488
+ healthItems.push("Issues:");
1489
+ stats.health.issues.forEach((issue) => {
1490
+ healthItems.push(` ${issue.message}`);
1491
+ });
1492
+ }
1493
+ sections.push({ header: "Health Score", items: healthItems });
1494
+ }
1495
+ const topRepos = Object.values(stats.byRepository).sort((a, b) => b.lines - a.lines).slice(0, 5).map((repo) => `${repo.name}: ${repo.lines.toLocaleString()} lines, ${repo.packages} pkg(s)`);
1496
+ if (topRepos.length > 0) {
1497
+ sections.push({ header: "Top Repositories", items: topRepos });
1498
+ }
1499
+ ui?.success?.("Statistics collected successfully", {
1500
+ title: "\u{1F4CA} KB Labs Monorepo Statistics",
1501
+ sections
1502
+ });
1503
+ }
1504
+ function outputMarkdown(stats) {
1505
+ console.log("# KB Labs Monorepo Statistics\n");
1506
+ console.log("## Overview\n");
1507
+ console.log("| Metric | Value |");
1508
+ console.log("|--------|-------|");
1509
+ console.log(`| Packages | ${stats.overview.totalPackages} |`);
1510
+ console.log(`| Repositories | ${stats.overview.totalRepositories} |`);
1511
+ console.log(`| Lines of Code | ${stats.overview.totalLines.toLocaleString()} |`);
1512
+ console.log(`| Total Size | ${formatBytes(stats.overview.totalBytes)} |
1513
+ `);
1514
+ if (stats.health) {
1515
+ console.log("## Health Score\n");
1516
+ console.log(`**${stats.health.score}/100** (Grade ${stats.health.grade})
1517
+ `);
1518
+ if (stats.health.issues.length > 0) {
1519
+ console.log("### Issues\n");
1520
+ for (const issue of stats.health.issues) {
1521
+ console.log(`- ${issue.message}`);
1522
+ }
1523
+ console.log("");
1524
+ }
1525
+ }
1526
+ console.log("## By Repository\n");
1527
+ console.log("| Repository | Packages | Lines | Size |");
1528
+ console.log("|------------|----------|-------|------|");
1529
+ for (const [name, repo] of Object.entries(stats.byRepository)) {
1530
+ console.log(`| ${name} | ${repo.packages} | ${repo.lines.toLocaleString()} | ${formatBytes(repo.bytes)} |`);
1531
+ }
1532
+ }
1533
+ function formatBytes(bytes) {
1534
+ if (bytes === 0) {
1535
+ return "0 B";
1536
+ }
1537
+ const k = 1024;
1538
+ const sizes = ["B", "KB", "MB", "GB"];
1539
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1540
+ return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
1541
+ }
1542
+ var visualize_default = defineCommand({
1543
+ id: "quality:visualize",
1544
+ description: "Visualize dependency graph",
1545
+ handler: {
1546
+ async execute(ctx, input) {
1547
+ const { ui } = ctx;
1548
+ const flags = input.flags ?? input;
1549
+ const graph = buildDependencyGraph(ctx.cwd);
1550
+ if (flags.dot) {
1551
+ outputDot(graph, flags, ui);
1552
+ } else if (flags.tree && flags.package) {
1553
+ outputTree(graph, flags.package, flags, ui);
1554
+ } else if (flags.reverse && flags.package) {
1555
+ outputReverse(graph, flags.package, flags, ui);
1556
+ } else if (flags.impact && flags.package) {
1557
+ outputImpact(graph, flags.package, flags, ui);
1558
+ } else if (flags.stats) {
1559
+ outputStats3(graph, flags, ui);
1560
+ } else {
1561
+ outputStats3(graph, flags, ui);
1562
+ }
1563
+ return { exitCode: 0 };
1564
+ }
1565
+ }
1566
+ });
1567
+ function outputTree(graph, packageName, flags, ui) {
1568
+ const { nodes } = graph;
1569
+ const node = nodes.get(packageName);
1570
+ if (!node) {
1571
+ ui?.fatal?.(`Package not found: ${packageName}`);
1572
+ return;
1573
+ }
1574
+ const sections = [];
1575
+ const visited = /* @__PURE__ */ new Set();
1576
+ const treeItems = [];
1577
+ function buildTree(pkg, depth, prefix) {
1578
+ if (visited.has(pkg)) {
1579
+ treeItems.push(`${prefix}${pkg} (circular)`);
1580
+ return;
1581
+ }
1582
+ visited.add(pkg);
1583
+ const pkgNode = nodes.get(pkg);
1584
+ if (!pkgNode) {
1585
+ return;
1586
+ }
1587
+ treeItems.push(`${prefix}${pkg}`);
1588
+ const deps = Array.from(pkgNode.deps);
1589
+ for (let i = 0; i < deps.length; i++) {
1590
+ const dep = deps[i];
1591
+ if (!dep) {
1592
+ continue;
1593
+ }
1594
+ const isLast = i === deps.length - 1;
1595
+ const newPrefix = prefix + (isLast ? " \u2514\u2500 " : " \u251C\u2500 ");
1596
+ buildTree(dep, depth + 1, newPrefix);
1597
+ }
1598
+ }
1599
+ buildTree(packageName, 0, "");
1600
+ sections.push({
1601
+ header: "Dependency Tree",
1602
+ items: treeItems
1603
+ });
1604
+ ui?.success?.("Dependency tree generated", {
1605
+ title: `\u{1F4E6} Dependency Tree for ${packageName}`,
1606
+ sections
1607
+ });
1608
+ }
1609
+ function outputReverse(graph, packageName, flags, ui) {
1610
+ const reverseDeps = getReverseDependencies(graph, packageName);
1611
+ if (flags.json) {
1612
+ ui?.json?.({ package: packageName, reverseDependencies: Array.from(reverseDeps) });
1613
+ return;
1614
+ }
1615
+ const sections = [];
1616
+ if (reverseDeps.size === 0) {
1617
+ sections.push({
1618
+ header: "Result",
1619
+ items: ["No packages depend on this package"]
1620
+ });
1621
+ } else {
1622
+ sections.push({
1623
+ header: `Packages that depend on ${packageName}`,
1624
+ items: Array.from(reverseDeps).map((dep) => `\u2022 ${dep}`)
1625
+ });
1626
+ }
1627
+ ui?.success?.("Reverse dependencies found", {
1628
+ title: `\u2B05\uFE0F Reverse Dependencies for ${packageName}`,
1629
+ sections
1630
+ });
1631
+ }
1632
+ function outputImpact(graph, packageName, flags, ui) {
1633
+ const affected = getImpactAnalysis(graph, packageName);
1634
+ if (flags.json) {
1635
+ ui?.json?.({ package: packageName, affectedPackages: Array.from(affected) });
1636
+ return;
1637
+ }
1638
+ const sections = [];
1639
+ if (affected.size === 0) {
1640
+ sections.push({
1641
+ header: "Result",
1642
+ items: ["No other packages affected by changes to this package"]
1643
+ });
1644
+ } else {
1645
+ sections.push({
1646
+ header: `Impact Analysis for ${packageName}`,
1647
+ items: [
1648
+ `Changes to this package will affect ${affected.size} other packages:`,
1649
+ "",
1650
+ ...Array.from(affected).map((dep) => `\u2022 ${dep}`)
1651
+ ]
1652
+ });
1653
+ sections.push({
1654
+ header: "Recommendations",
1655
+ items: [
1656
+ "\u{1F9EA} Run tests in affected packages",
1657
+ "\u{1F528} Rebuild affected packages",
1658
+ "\u{1F4DD} Update documentation if API changed"
1659
+ ]
1660
+ });
1661
+ }
1662
+ ui?.success?.("Impact analysis completed", {
1663
+ title: `\u{1F4A5} Impact Analysis for ${packageName}`,
1664
+ sections
1665
+ });
1666
+ }
1667
+ function outputStats3(graph, flags, ui) {
1668
+ const { nodes } = graph;
1669
+ let totalDeps = 0;
1670
+ let maxDeps = 0;
1671
+ let maxDepsPackage = "";
1672
+ const depCounts = /* @__PURE__ */ new Map();
1673
+ for (const [name, node] of nodes) {
1674
+ const depCount = node.deps.size;
1675
+ totalDeps += depCount;
1676
+ if (depCount > maxDeps) {
1677
+ maxDeps = depCount;
1678
+ maxDepsPackage = name;
1679
+ }
1680
+ depCounts.set(depCount, (depCounts.get(depCount) ?? 0) + 1);
1681
+ }
1682
+ const avgDeps = nodes.size > 0 ? (totalDeps / nodes.size).toFixed(2) : "0";
1683
+ if (flags.json) {
1684
+ ui?.json?.({
1685
+ totalPackages: nodes.size,
1686
+ totalDependencies: totalDeps,
1687
+ averageDependencies: Number.parseFloat(avgDeps),
1688
+ maxDependencies: maxDeps,
1689
+ maxDependenciesPackage: maxDepsPackage
1690
+ });
1691
+ return;
1692
+ }
1693
+ const sections = [];
1694
+ sections.push({
1695
+ header: "Graph Statistics",
1696
+ items: [
1697
+ `Total packages: ${nodes.size}`,
1698
+ `Total dependencies: ${totalDeps}`,
1699
+ `Average dependencies per package: ${avgDeps}`,
1700
+ `Max dependencies: ${maxDeps} (${maxDepsPackage})`
1701
+ ]
1702
+ });
1703
+ const distributionItems = [];
1704
+ const sortedCounts = Array.from(depCounts.entries()).sort((a, b) => a[0] - b[0]);
1705
+ for (const [count, packages] of sortedCounts) {
1706
+ const bar = "\u2588".repeat(Math.min(packages, 20));
1707
+ distributionItems.push(`${count} deps: ${bar} ${packages} packages`);
1708
+ }
1709
+ sections.push({
1710
+ header: "Dependency Distribution",
1711
+ items: distributionItems
1712
+ });
1713
+ ui?.success?.("Graph statistics calculated", {
1714
+ title: "\u{1F4CA} Dependency Graph Statistics",
1715
+ sections
1716
+ });
1717
+ }
1718
+ function outputDot(graph, flags, ui) {
1719
+ const { nodes } = graph;
1720
+ const lines = [];
1721
+ lines.push("digraph dependencies {");
1722
+ lines.push(" rankdir=LR;");
1723
+ lines.push(" node [shape=box];");
1724
+ lines.push("");
1725
+ for (const [name, node] of nodes) {
1726
+ for (const dep of node.deps) {
1727
+ const safeName = name.replace(/[@\/]/g, "_");
1728
+ const safeDep = dep.replace(/[@\/]/g, "_");
1729
+ lines.push(` "${safeName}" -> "${safeDep}";`);
1730
+ }
1731
+ }
1732
+ lines.push("}");
1733
+ if (flags.json) {
1734
+ ui?.json?.({ dot: lines.join("\n") });
1735
+ } else {
1736
+ for (const line of lines) {
1737
+ ui?.write?.(line);
1738
+ }
1739
+ ui?.write?.("");
1740
+ ui?.write?.("# Save to file and visualize with:");
1741
+ ui?.write?.("# dot -Tpng dependencies.dot -o dependencies.png");
1742
+ }
1743
+ }
1744
+
1745
+ export { build_order_default as buildOrder, check_builds_default as checkBuilds, check_tests_default as checkTests, check_types_default as checkTypes, cycles_default as cycles, dead_code_default as deadCode, fix_deps_default as fixDeps, health_default as health, stats_default as stats, visualize_default as visualize };
1746
+ //# sourceMappingURL=index.js.map
1747
+ //# sourceMappingURL=index.js.map