@kb-labs/quality-entry 2.94.0 → 2.96.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 (98) hide show
  1. package/dist/cli/commands/build-order.d.ts +5 -12
  2. package/dist/cli/commands/build-order.js +45 -97
  3. package/dist/cli/commands/build-order.js.map +1 -1
  4. package/dist/cli/commands/check-layers.d.ts +9 -0
  5. package/dist/cli/commands/check-layers.js +48 -0
  6. package/dist/cli/commands/check-layers.js.map +1 -0
  7. package/dist/cli/commands/context.d.ts +9 -0
  8. package/dist/cli/commands/context.js +94 -0
  9. package/dist/cli/commands/context.js.map +1 -0
  10. package/dist/cli/commands/coupling.d.ts +9 -0
  11. package/dist/cli/commands/coupling.js +35 -0
  12. package/dist/cli/commands/coupling.js.map +1 -0
  13. package/dist/cli/commands/dead-code.d.ts +4 -5
  14. package/dist/cli/commands/dead-code.js +41 -197
  15. package/dist/cli/commands/dead-code.js.map +1 -1
  16. package/dist/cli/commands/fix-deps.d.ts +2 -5
  17. package/dist/cli/commands/fix-deps.js +74 -83
  18. package/dist/cli/commands/fix-deps.js.map +1 -1
  19. package/dist/cli/commands/flags.d.ts +115 -1
  20. package/dist/cli/commands/flags.js +46 -1
  21. package/dist/cli/commands/flags.js.map +1 -1
  22. package/dist/cli/commands/gate.d.ts +9 -0
  23. package/dist/cli/commands/gate.js +59 -0
  24. package/dist/cli/commands/gate.js.map +1 -0
  25. package/dist/cli/commands/health.d.ts +4 -5
  26. package/dist/cli/commands/health.js +42 -191
  27. package/dist/cli/commands/health.js.map +1 -1
  28. package/dist/cli/commands/history.d.ts +9 -0
  29. package/dist/cli/commands/history.js +55 -0
  30. package/dist/cli/commands/history.js.map +1 -0
  31. package/dist/cli/commands/index.d.ts +6 -2
  32. package/dist/cli/commands/index.js +411 -828
  33. package/dist/cli/commands/index.js.map +1 -1
  34. package/dist/cli/commands/snapshot.d.ts +9 -0
  35. package/dist/cli/commands/snapshot.js +91 -0
  36. package/dist/cli/commands/snapshot.js.map +1 -0
  37. package/dist/cli/commands/stats.d.ts +4 -5
  38. package/dist/cli/commands/stats.js +17 -265
  39. package/dist/cli/commands/stats.js.map +1 -1
  40. package/dist/index.d.ts +1 -1
  41. package/dist/index.js +198 -306
  42. package/dist/index.js.map +1 -1
  43. package/dist/manifest.d.ts +95 -82
  44. package/dist/manifest.js +198 -306
  45. package/dist/manifest.js.map +1 -1
  46. package/dist/rest/handlers/build-order-handler.d.ts +5 -13
  47. package/dist/rest/handlers/build-order-handler.js +14 -17
  48. package/dist/rest/handlers/build-order-handler.js.map +1 -1
  49. package/dist/rest/handlers/builds-handler.js +10 -6
  50. package/dist/rest/handlers/builds-handler.js.map +1 -1
  51. package/dist/rest/handlers/coupling-handler.d.ts +8 -0
  52. package/dist/rest/handlers/coupling-handler.js +28 -0
  53. package/dist/rest/handlers/coupling-handler.js.map +1 -0
  54. package/dist/rest/handlers/cycles-handler.js +16 -12
  55. package/dist/rest/handlers/cycles-handler.js.map +1 -1
  56. package/dist/rest/handlers/dependencies-handler.js +12 -8
  57. package/dist/rest/handlers/dependencies-handler.js.map +1 -1
  58. package/dist/rest/handlers/graph-handler.js +77 -73
  59. package/dist/rest/handlers/graph-handler.js.map +1 -1
  60. package/dist/rest/handlers/health-handler.d.ts +4 -11
  61. package/dist/rest/handlers/health-handler.js +35 -9
  62. package/dist/rest/handlers/health-handler.js.map +1 -1
  63. package/dist/rest/handlers/history-handler.d.ts +7 -0
  64. package/dist/rest/handlers/history-handler.js +21 -0
  65. package/dist/rest/handlers/history-handler.js.map +1 -0
  66. package/dist/rest/handlers/knip-handler.d.ts +8 -0
  67. package/dist/rest/handlers/knip-handler.js +31 -0
  68. package/dist/rest/handlers/knip-handler.js.map +1 -0
  69. package/dist/rest/handlers/layers-handler.d.ts +8 -0
  70. package/dist/rest/handlers/layers-handler.js +28 -0
  71. package/dist/rest/handlers/layers-handler.js.map +1 -0
  72. package/dist/rest/handlers/snapshot-handler.d.ts +7 -0
  73. package/dist/rest/handlers/snapshot-handler.js +67 -0
  74. package/dist/rest/handlers/snapshot-handler.js.map +1 -0
  75. package/dist/rest/handlers/stale-handler.js +13 -9
  76. package/dist/rest/handlers/stale-handler.js.map +1 -1
  77. package/dist/rest/handlers/stats-handler.d.ts +4 -9
  78. package/dist/rest/handlers/stats-handler.js +22 -18
  79. package/dist/rest/handlers/stats-handler.js.map +1 -1
  80. package/dist/rest/handlers/tests-handler.js +12 -8
  81. package/dist/rest/handlers/tests-handler.js.map +1 -1
  82. package/dist/rest/handlers/types-handler.js +10 -6
  83. package/dist/rest/handlers/types-handler.js.map +1 -1
  84. package/dist/widgets/117.js +24 -1
  85. package/dist/widgets/117.js.map +1 -1
  86. package/dist/widgets/370.js +1 -1
  87. package/dist/widgets/370.js.map +1 -1
  88. package/dist/widgets/62.js +97 -0
  89. package/dist/widgets/62.js.map +1 -0
  90. package/dist/widgets/__federation_expose_QualityOverview.js +1 -1
  91. package/dist/widgets/__federation_expose_QualityOverview.js.map +1 -1
  92. package/dist/widgets/mf-manifest.json +27 -27
  93. package/dist/widgets/mf-stats.json +27 -27
  94. package/dist/widgets/remoteEntry.js +2 -2
  95. package/dist/widgets/remoteEntry.js.map +1 -1
  96. package/package.json +9 -8
  97. package/dist/widgets/418.js +0 -39
  98. package/dist/widgets/418.js.map +0 -1
@@ -1,216 +1,60 @@
1
- import { defineCommand } from '@kb-labs/sdk';
2
- import { CACHE_KEYS } from '@kb-labs/quality-contracts';
3
- import { listBackups, restoreFromBackup, scanDeadFiles, removeDeadFiles } from '@kb-labs/quality-core/dead-code';
1
+ import { defineCommand, useConfig } from '@kb-labs/sdk';
2
+ import { runKnip } from '@kb-labs/quality-core';
3
+ import { defaultQualityConfig } from '@kb-labs/quality-contracts';
4
4
 
5
5
  // src/cli/commands/dead-code.ts
6
6
  var dead_code_default = defineCommand({
7
7
  id: "quality:dead-code",
8
- description: "Detect and optionally remove dead (unreachable) source files",
8
+ description: "Detect unused files, exports, and deps via knip",
9
9
  handler: {
10
10
  async execute(ctx, input) {
11
- const { ui, platform } = ctx;
12
- const flags = "flags" in input && typeof input.flags === "object" && input.flags !== null ? input.flags : input;
13
- if (flags["list-backups"]) {
14
- const backups = listBackups(ctx.cwd);
15
- if (flags.json) {
16
- ui?.json?.(backups);
17
- } else {
18
- outputBackupList(backups, ui);
19
- }
20
- return { exitCode: 0, result: backups };
11
+ const { flags } = input;
12
+ const config = await useConfig();
13
+ const cwd = ctx.cwd ?? process.cwd();
14
+ const cfg = config ?? defaultQualityConfig;
15
+ if (!cfg.knip.enabled) {
16
+ ctx.ui?.success?.("knip is disabled in config (knip.enabled = false)");
17
+ return { exitCode: 0 };
21
18
  }
22
- if (flags.restore) {
23
- try {
24
- const result2 = await restoreFromBackup(ctx.cwd, flags.restore);
25
- if (flags.json) {
26
- ui?.json?.(result2);
27
- } else {
28
- ui?.success?.(`Restored ${result2.restoredFiles} file(s) from backup ${flags.restore}`, {});
29
- if (result2.restoredExports > 0) {
30
- ui?.info?.(`Note: ${result2.restoredExports} package.json export(s) were cleaned. You may need to rebuild.`);
31
- }
32
- }
33
- return { exitCode: 0 };
34
- } catch (err) {
35
- if (flags.json) {
36
- ui?.json?.({ error: err instanceof Error ? err.message : String(err) });
37
- } else {
38
- ui?.error?.(err instanceof Error ? err.message : String(err));
39
- }
40
- return { exitCode: 1 };
41
- }
19
+ const report = await runKnip({ shell: ctx.api.shell, rootDir: cwd });
20
+ if (flags.json) {
21
+ ctx.ui?.json?.(report);
22
+ return { exitCode: report.totalIssues > 0 ? 1 : 0 };
42
23
  }
43
- const cacheKey = `${CACHE_KEYS.DEAD_CODE}:${flags.package || "all"}`;
44
- if (!flags.refresh && !flags["auto-remove"]) {
45
- const cached = await platform.cache.get(cacheKey);
46
- if (cached) {
47
- outputDeadCodeReport({ ...cached, cached: true }, flags, ui);
48
- return { exitCode: cached.summary.totalDead > 0 ? 1 : 0, result: cached };
49
- }
24
+ if (report.totalIssues === 0) {
25
+ ctx.ui?.success?.("No dead code detected");
26
+ return { exitCode: 0 };
50
27
  }
51
- const result = await scanDeadFiles(ctx.cwd, {
52
- packageFilter: flags.package,
53
- verbose: flags.verbose
54
- });
55
- await platform.cache.set(cacheKey, result, 5 * 60 * 1e3);
56
- await platform.analytics.track("quality:dead-code", {
57
- totalPackages: result.summary.totalPackages,
58
- totalFiles: result.summary.totalFiles,
59
- totalDead: result.summary.totalDead,
60
- totalDeadBytes: result.summary.totalDeadBytes,
61
- duration: result.duration,
62
- autoRemove: !!flags["auto-remove"],
63
- dryRun: !!flags["dry-run"]
64
- });
65
- if (flags["auto-remove"]) {
66
- if (result.summary.totalDead === 0) {
67
- if (flags.json) {
68
- ui?.json?.({ message: "No dead files found", ...result });
69
- } else {
70
- ui?.success?.("No dead files found. Nothing to remove.", {});
71
- }
72
- return { exitCode: 0, result };
73
- }
74
- const removalResult = await removeDeadFiles(ctx.cwd, result, {
75
- dryRun: flags["dry-run"]
28
+ const sections = [];
29
+ if (report.unusedFiles.length > 0) {
30
+ sections.push({
31
+ header: `Unused files (${report.unusedFiles.length})`,
32
+ items: report.unusedFiles
76
33
  });
77
- if (flags.json) {
78
- ui?.json?.(removalResult);
79
- } else {
80
- outputRemovalReport(removalResult, flags, ui);
81
- }
82
- return { exitCode: 0, result: removalResult };
83
34
  }
84
- outputDeadCodeReport({ ...result, cached: false }, flags, ui);
85
- return {
86
- exitCode: result.summary.totalDead > 0 ? 1 : 0,
87
- result
88
- };
89
- }
90
- }
91
- });
92
- function outputDeadCodeReport(result, flags, ui) {
93
- if (flags.json) {
94
- ui?.json?.(result);
95
- return;
96
- }
97
- const sections = [];
98
- const packagesWithDead = result.packages.filter((p) => p.deadFiles.length > 0);
99
- if (packagesWithDead.length > 0) {
100
- const items = [];
101
- for (const pkg of packagesWithDead) {
102
- items.push(`${pkg.packageName} (${pkg.deadFiles.length} dead file${pkg.deadFiles.length === 1 ? "" : "s"})`);
103
- for (const dead of pkg.deadFiles.slice(0, 5)) {
104
- const sizeKb = (dead.sizeBytes / 1024).toFixed(1);
105
- items.push(` ${dead.relativePath} (${sizeKb} KB)`);
35
+ if (report.unusedExports.length > 0) {
36
+ sections.push({
37
+ header: `Unused exports (${report.unusedExports.length})`,
38
+ items: report.unusedExports.map((e) => `${e.file} ${e.symbol}`)
39
+ });
106
40
  }
107
- if (pkg.deadFiles.length > 5) {
108
- items.push(` ... and ${pkg.deadFiles.length - 5} more`);
41
+ if (report.unusedDependencies.length > 0) {
42
+ sections.push({
43
+ header: `Unused dependencies (${report.unusedDependencies.length})`,
44
+ items: report.unusedDependencies.map((d) => `${d.package} in ${d.workspace}`)
45
+ });
109
46
  }
110
- }
111
- sections.push({ header: "Dead Files", items });
112
- }
113
- if (flags.verbose) {
114
- for (const pkg of result.packages) {
115
- if (pkg.entryPoints.length > 0) {
116
- const items = pkg.entryPoints.slice(0, 10).map((ep) => ` ${ep}`);
117
- if (pkg.entryPoints.length > 10) {
118
- items.push(` ... and ${pkg.entryPoints.length - 10} more`);
119
- }
120
- sections.push({ header: `Entry Points: ${pkg.packageName}`, items });
47
+ if (report.unlistedDependencies.length > 0) {
48
+ sections.push({
49
+ header: `Unlisted dependencies (${report.unlistedDependencies.length})`,
50
+ items: report.unlistedDependencies.map((d) => `${d.package} in ${d.workspace}`)
51
+ });
121
52
  }
53
+ ctx.ui?.error?.(`${report.totalIssues} dead code issue(s) found`, { sections });
54
+ return { exitCode: 1 };
122
55
  }
123
56
  }
124
- const allWarnings = result.packages.flatMap((p) => p.warnings);
125
- if (allWarnings.length > 0) {
126
- sections.push({
127
- header: "Warnings",
128
- items: allWarnings.slice(0, 10)
129
- });
130
- }
131
- const totalDeadKb = (result.summary.totalDeadBytes / 1024).toFixed(1);
132
- const summaryItems = [
133
- `Packages scanned: ${result.summary.totalPackages}`,
134
- `Total source files: ${result.summary.totalFiles}`,
135
- `Alive files: ${result.summary.totalAlive}`,
136
- `Dead files: ${result.summary.totalDead}`,
137
- result.summary.totalDead > 0 ? `Dead code size: ${totalDeadKb} KB` : null,
138
- result.summary.emptyDirectories.length > 0 ? `Empty directories: ${result.summary.emptyDirectories.length}` : null,
139
- `Duration: ${(result.duration / 1e3).toFixed(1)}s`,
140
- result.cached ? "(cached \u2014 use --refresh to rescan)" : null
141
- ].filter(Boolean);
142
- sections.push({ header: "Summary", items: summaryItems });
143
- if (result.summary.totalDead > 0) {
144
- sections.push({
145
- header: "Next Steps",
146
- items: [
147
- "Run with --auto-remove --dry-run to preview removal",
148
- "Run with --auto-remove to remove and create backup",
149
- "Run with --verbose to see entry points"
150
- ]
151
- });
152
- }
153
- const title = result.summary.totalDead === 0 ? "No dead files found" : `${result.summary.totalDead} dead file(s) found`;
154
- ui?.success?.("Dead code analysis completed", { title, sections });
155
- }
156
- function outputRemovalReport(result, flags, ui) {
157
- const isDryRun = flags["dry-run"];
158
- const prefix = isDryRun ? "[DRY RUN] Would" : "Successfully";
159
- const sections = [];
160
- const items = [
161
- `${prefix} remove ${result.filesRemoved} file(s)`,
162
- `${prefix} free ${(result.bytesRemoved / 1024).toFixed(1)} KB`
163
- ];
164
- if (result.emptyDirsRemoved > 0) {
165
- items.push(`${prefix} remove ${result.emptyDirsRemoved} empty director${result.emptyDirsRemoved === 1 ? "y" : "ies"}`);
166
- }
167
- if (result.exportsCleanedUp > 0) {
168
- items.push(`${prefix} clean ${result.exportsCleanedUp} package.json export(s)`);
169
- }
170
- sections.push({ header: isDryRun ? "Preview" : "Removal Complete", items });
171
- if (!isDryRun) {
172
- sections.push({
173
- header: "Backup",
174
- items: [
175
- `Backup ID: ${result.backupId}`,
176
- `To restore: pnpm kb quality:dead-code --restore ${result.backupId}`
177
- ]
178
- });
179
- }
180
- const fileItems = [];
181
- for (const file of result.manifest.removedFiles.slice(0, 10)) {
182
- const sizeKb = (file.sizeBytes / 1024).toFixed(1);
183
- fileItems.push(`${file.backupPath} (${sizeKb} KB)`);
184
- }
185
- if (result.manifest.removedFiles.length > 10) {
186
- fileItems.push(`... and ${result.manifest.removedFiles.length - 10} more`);
187
- }
188
- sections.push({ header: "Files", items: fileItems });
189
- const title = isDryRun ? `[DRY RUN] Would remove ${result.filesRemoved} file(s)` : `Removed ${result.filesRemoved} file(s)`;
190
- ui?.success?.(title, { title, sections });
191
- }
192
- function outputBackupList(backups, ui) {
193
- if (backups.length === 0) {
194
- ui?.info?.("No backups found.");
195
- return;
196
- }
197
- const sections = [];
198
- for (const backup of backups) {
199
- const date = new Date(backup.createdAt).toLocaleString();
200
- const items = [
201
- `Date: ${date}`,
202
- `Git: ${backup.gitBranch} @ ${backup.gitSha.slice(0, 8)}`,
203
- `Files: ${backup.totalFilesRemoved}`,
204
- `Size: ${(backup.totalBytesRemoved / 1024).toFixed(1)} KB`,
205
- `Restore: pnpm kb quality:dead-code --restore ${backup.id}`
206
- ];
207
- sections.push({ header: `Backup: ${backup.id}`, items });
208
- }
209
- ui?.success?.(`${backups.length} backup(s) found`, {
210
- title: "Dead Code Backups",
211
- sections
212
- });
213
- }
57
+ });
214
58
 
215
59
  export { dead_code_default as default };
216
60
  //# sourceMappingURL=dead-code.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/cli/commands/dead-code.ts"],"names":["result"],"mappings":"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","file":"dead-code.js","sourcesContent":["/**\n * quality:dead-code - Detect and optionally remove dead source files\n *\n * Uses reachability analysis: builds import graph, walks from entry points,\n * files not reachable are dead. Zero false positives by design.\n */\n\nimport { defineCommand, type PluginContextV3 } from '@kb-labs/sdk';\nimport type { UIFacade } from '@kb-labs/sdk';\nimport type { DeadCodeResult, DeadCodeRemovalResult, DeadCodeBackupManifest } from '@kb-labs/quality-contracts';\nimport { CACHE_KEYS } from '@kb-labs/quality-contracts';\nimport {\n scanDeadFiles,\n removeDeadFiles,\n restoreFromBackup,\n listBackups,\n} from '@kb-labs/quality-core/dead-code';\nimport type { DeadCodeFlags } from './flags.js';\n\ntype DeadCodeInput = DeadCodeFlags & { argv?: string[] };\n\ntype DeadCodeCommandResult = {\n exitCode: number;\n result?: DeadCodeResult | DeadCodeRemovalResult | DeadCodeBackupManifest[];\n meta?: Record<string, unknown>;\n};\n\nexport default defineCommand({\n id: 'quality:dead-code',\n description: 'Detect and optionally remove dead (unreachable) source files',\n\n handler: {\n async execute(\n ctx: PluginContextV3,\n input: DeadCodeInput,\n ): Promise<DeadCodeCommandResult> {\n const { ui, platform } = ctx;\n // V3: Flags may come wrapped in input.flags or passed directly\n const flags = ('flags' in input && typeof (input as { flags?: unknown }).flags === 'object' && (input as { flags?: unknown }).flags !== null)\n ? (input as { flags: DeadCodeInput }).flags\n : input;\n\n // --- Sub-operations ---\n\n // List backups\n if (flags['list-backups']) {\n const backups = listBackups(ctx.cwd);\n if (flags.json) {\n ui?.json?.(backups);\n } else {\n outputBackupList(backups, ui);\n }\n return { exitCode: 0, result: backups };\n }\n\n // Restore from backup\n if (flags.restore) {\n try {\n const result = await restoreFromBackup(ctx.cwd, flags.restore);\n if (flags.json) {\n ui?.json?.(result);\n } else {\n ui?.success?.(`Restored ${result.restoredFiles} file(s) from backup ${flags.restore}`, {});\n if (result.restoredExports > 0) {\n ui?.info?.(`Note: ${result.restoredExports} package.json export(s) were cleaned. You may need to rebuild.`);\n }\n }\n return { exitCode: 0 };\n } catch (err: unknown) {\n if (flags.json) {\n ui?.json?.({ error: err instanceof Error ? err.message : String(err) });\n } else {\n ui?.error?.(err instanceof Error ? err.message : String(err));\n }\n return { exitCode: 1 };\n }\n }\n\n // --- Main scan flow ---\n\n // Check cache\n const cacheKey = `${CACHE_KEYS.DEAD_CODE}:${flags.package || 'all'}`;\n if (!flags.refresh && !flags['auto-remove']) {\n const cached = await platform.cache.get<DeadCodeResult>(cacheKey);\n if (cached) {\n outputDeadCodeReport({ ...cached, cached: true }, flags, ui);\n return { exitCode: cached.summary.totalDead > 0 ? 1 : 0, result: cached };\n }\n }\n\n // Run scan\n const result = await scanDeadFiles(ctx.cwd, {\n packageFilter: flags.package,\n verbose: flags.verbose,\n });\n\n // Cache results for 5 minutes\n await platform.cache.set(cacheKey, result, 5 * 60 * 1000);\n\n // Track analytics\n await platform.analytics.track('quality:dead-code', {\n totalPackages: result.summary.totalPackages,\n totalFiles: result.summary.totalFiles,\n totalDead: result.summary.totalDead,\n totalDeadBytes: result.summary.totalDeadBytes,\n duration: result.duration,\n autoRemove: !!flags['auto-remove'],\n dryRun: !!flags['dry-run'],\n });\n\n // Auto-remove flow\n if (flags['auto-remove']) {\n if (result.summary.totalDead === 0) {\n if (flags.json) {\n ui?.json?.({ message: 'No dead files found', ...result });\n } else {\n ui?.success?.('No dead files found. Nothing to remove.', {});\n }\n return { exitCode: 0, result };\n }\n\n const removalResult = await removeDeadFiles(ctx.cwd, result, {\n dryRun: flags['dry-run'],\n });\n\n if (flags.json) {\n ui?.json?.(removalResult);\n } else {\n outputRemovalReport(removalResult, flags, ui);\n }\n\n return { exitCode: 0, result: removalResult };\n }\n\n // Output scan results\n outputDeadCodeReport({ ...result, cached: false }, flags, ui);\n\n return {\n exitCode: result.summary.totalDead > 0 ? 1 : 0,\n result,\n };\n },\n },\n});\n\n// --- Output helpers ---\n\nfunction outputDeadCodeReport(\n result: DeadCodeResult & { cached?: boolean },\n flags: DeadCodeFlags,\n ui: UIFacade | undefined,\n): void {\n if (flags.json) {\n ui?.json?.(result);\n return;\n }\n\n const sections: Array<{ header: string; items: string[] }> = [];\n\n // Per-package results (only packages with dead files)\n const packagesWithDead = result.packages.filter(p => p.deadFiles.length > 0);\n\n if (packagesWithDead.length > 0) {\n const items: string[] = [];\n for (const pkg of packagesWithDead) {\n items.push(`${pkg.packageName} (${pkg.deadFiles.length} dead file${pkg.deadFiles.length === 1 ? '' : 's'})`);\n for (const dead of pkg.deadFiles.slice(0, 5)) {\n const sizeKb = (dead.sizeBytes / 1024).toFixed(1);\n items.push(` ${dead.relativePath} (${sizeKb} KB)`);\n }\n if (pkg.deadFiles.length > 5) {\n items.push(` ... and ${pkg.deadFiles.length - 5} more`);\n }\n }\n sections.push({ header: 'Dead Files', items });\n }\n\n // Verbose: show entry points\n if (flags.verbose) {\n for (const pkg of result.packages) {\n if (pkg.entryPoints.length > 0) {\n const items = pkg.entryPoints.slice(0, 10).map(ep => ` ${ep}`);\n if (pkg.entryPoints.length > 10) {\n items.push(` ... and ${pkg.entryPoints.length - 10} more`);\n }\n sections.push({ header: `Entry Points: ${pkg.packageName}`, items });\n }\n }\n }\n\n // Warnings\n const allWarnings = result.packages.flatMap(p => p.warnings);\n if (allWarnings.length > 0) {\n sections.push({\n header: 'Warnings',\n items: allWarnings.slice(0, 10),\n });\n }\n\n // Summary\n const totalDeadKb = (result.summary.totalDeadBytes / 1024).toFixed(1);\n const summaryItems = [\n `Packages scanned: ${result.summary.totalPackages}`,\n `Total source files: ${result.summary.totalFiles}`,\n `Alive files: ${result.summary.totalAlive}`,\n `Dead files: ${result.summary.totalDead}`,\n result.summary.totalDead > 0 ? `Dead code size: ${totalDeadKb} KB` : null,\n result.summary.emptyDirectories.length > 0\n ? `Empty directories: ${result.summary.emptyDirectories.length}`\n : null,\n `Duration: ${(result.duration / 1000).toFixed(1)}s`,\n result.cached ? '(cached — use --refresh to rescan)' : null,\n ].filter(Boolean) as string[];\n sections.push({ header: 'Summary', items: summaryItems });\n\n // Recommendations\n if (result.summary.totalDead > 0) {\n sections.push({\n header: 'Next Steps',\n items: [\n 'Run with --auto-remove --dry-run to preview removal',\n 'Run with --auto-remove to remove and create backup',\n 'Run with --verbose to see entry points',\n ],\n });\n }\n\n const title =\n result.summary.totalDead === 0\n ? 'No dead files found'\n : `${result.summary.totalDead} dead file(s) found`;\n\n ui?.success?.('Dead code analysis completed', { title, sections });\n}\n\nfunction outputRemovalReport(\n result: DeadCodeRemovalResult,\n flags: DeadCodeFlags,\n ui: UIFacade | undefined,\n): void {\n const isDryRun = flags['dry-run'];\n const prefix = isDryRun ? '[DRY RUN] Would' : 'Successfully';\n\n const sections: Array<{ header: string; items: string[] }> = [];\n\n const items = [\n `${prefix} remove ${result.filesRemoved} file(s)`,\n `${prefix} free ${(result.bytesRemoved / 1024).toFixed(1)} KB`,\n ];\n\n if (result.emptyDirsRemoved > 0) {\n items.push(`${prefix} remove ${result.emptyDirsRemoved} empty director${result.emptyDirsRemoved === 1 ? 'y' : 'ies'}`);\n }\n if (result.exportsCleanedUp > 0) {\n items.push(`${prefix} clean ${result.exportsCleanedUp} package.json export(s)`);\n }\n\n sections.push({ header: isDryRun ? 'Preview' : 'Removal Complete', items });\n\n if (!isDryRun) {\n sections.push({\n header: 'Backup',\n items: [\n `Backup ID: ${result.backupId}`,\n `To restore: pnpm kb quality:dead-code --restore ${result.backupId}`,\n ],\n });\n }\n\n // Show removed files\n const fileItems: string[] = [];\n for (const file of result.manifest.removedFiles.slice(0, 10)) {\n const sizeKb = (file.sizeBytes / 1024).toFixed(1);\n fileItems.push(`${file.backupPath} (${sizeKb} KB)`);\n }\n if (result.manifest.removedFiles.length > 10) {\n fileItems.push(`... and ${result.manifest.removedFiles.length - 10} more`);\n }\n sections.push({ header: 'Files', items: fileItems });\n\n const title = isDryRun\n ? `[DRY RUN] Would remove ${result.filesRemoved} file(s)`\n : `Removed ${result.filesRemoved} file(s)`;\n\n ui?.success?.(title, { title, sections });\n}\n\nfunction outputBackupList(\n backups: DeadCodeBackupManifest[],\n ui: UIFacade | undefined,\n): void {\n if (backups.length === 0) {\n ui?.info?.('No backups found.');\n return;\n }\n\n const sections: Array<{ header: string; items: string[] }> = [];\n\n for (const backup of backups) {\n const date = new Date(backup.createdAt).toLocaleString();\n const items = [\n `Date: ${date}`,\n `Git: ${backup.gitBranch} @ ${backup.gitSha.slice(0, 8)}`,\n `Files: ${backup.totalFilesRemoved}`,\n `Size: ${(backup.totalBytesRemoved / 1024).toFixed(1)} KB`,\n `Restore: pnpm kb quality:dead-code --restore ${backup.id}`,\n ];\n sections.push({ header: `Backup: ${backup.id}`, items });\n }\n\n ui?.success?.(`${backups.length} backup(s) found`, {\n title: 'Dead Code Backups',\n sections,\n });\n}\n"]}
1
+ {"version":3,"sources":["../../../src/cli/commands/dead-code.ts"],"names":[],"mappings":"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","file":"dead-code.js","sourcesContent":["import {\n defineCommand,\n useConfig,\n type CLIInput,\n type PluginContextV3,\n} from '@kb-labs/sdk';\nimport { runKnip } from '@kb-labs/quality-core';\nimport { defaultQualityConfig, type QualityPluginConfig } from '@kb-labs/quality-contracts';\nimport type { DeadCodeFlags } from './flags.js';\n\nexport default defineCommand<unknown, CLIInput<DeadCodeFlags>, { exitCode: number }>({\n id: 'quality:dead-code',\n description: 'Detect unused files, exports, and deps via knip',\n\n handler: {\n async execute(ctx: PluginContextV3, input: CLIInput<DeadCodeFlags>): Promise<{ exitCode: number }> {\n const { flags } = input;\n const config = await useConfig<QualityPluginConfig>();\n const cwd = ctx.cwd ?? process.cwd();\n const cfg = config ?? defaultQualityConfig;\n\n if (!cfg.knip.enabled) {\n ctx.ui?.success?.('knip is disabled in config (knip.enabled = false)');\n return { exitCode: 0 };\n }\n\n const report = await runKnip({ shell: ctx.api.shell, rootDir: cwd });\n\n if (flags.json) {\n ctx.ui?.json?.(report);\n return { exitCode: report.totalIssues > 0 ? 1 : 0 };\n }\n\n if (report.totalIssues === 0) {\n ctx.ui?.success?.('No dead code detected');\n return { exitCode: 0 };\n }\n\n const sections: Array<{ header: string; items: string[] }> = [];\n\n if (report.unusedFiles.length > 0) {\n sections.push({\n header: `Unused files (${report.unusedFiles.length})`,\n items: report.unusedFiles,\n });\n }\n\n if (report.unusedExports.length > 0) {\n sections.push({\n header: `Unused exports (${report.unusedExports.length})`,\n items: report.unusedExports.map(e => `${e.file} ${e.symbol}`),\n });\n }\n\n if (report.unusedDependencies.length > 0) {\n sections.push({\n header: `Unused dependencies (${report.unusedDependencies.length})`,\n items: report.unusedDependencies.map(d => `${d.package} in ${d.workspace}`),\n });\n }\n\n if (report.unlistedDependencies.length > 0) {\n sections.push({\n header: `Unlisted dependencies (${report.unlistedDependencies.length})`,\n items: report.unlistedDependencies.map(d => `${d.package} in ${d.workspace}`),\n });\n }\n\n ctx.ui?.error?.(`${report.totalIssues} dead code issue(s) found`, { sections });\n return { exitCode: 1 };\n },\n },\n});\n"]}
@@ -1,9 +1,7 @@
1
1
  import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
2
+ import { CLIInput } from '@kb-labs/sdk';
2
3
  import { FixDepsFlags } from './flags.js';
3
4
 
4
- type FixDepsInput = FixDepsFlags & {
5
- argv?: string[];
6
- };
7
5
  interface FixResult {
8
6
  packagesScanned: number;
9
7
  removedDeps: Array<{
@@ -21,8 +19,7 @@ interface FixResult {
21
19
  to: string;
22
20
  packages: string[];
23
21
  }>;
24
- dryRun: boolean;
25
22
  }
26
- declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, FixDepsInput, FixResult>;
23
+ declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, CLIInput<FixDepsFlags>, FixResult>;
27
24
 
28
25
  export { _default as default };
@@ -1,4 +1,4 @@
1
- import { defineCommand, useLoader } from '@kb-labs/sdk';
1
+ import { defineCommand, validationError, useLoader } from '@kb-labs/sdk';
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
 
@@ -7,10 +7,39 @@ var fix_deps_default = defineCommand({
7
7
  id: "quality:fix-deps",
8
8
  description: "Auto-fix dependency issues",
9
9
  handler: {
10
+ async intent(_ctx, input) {
11
+ const flags = input.flags;
12
+ const removeUnused = flags["remove-unused"] || flags.all;
13
+ const addMissing = flags["add-missing"] || flags.all;
14
+ const alignVersions = flags["align-versions"] || flags.all;
15
+ const packages = findPackages(_ctx.cwd);
16
+ const preview = await runAnalysis(packages, { removeUnused, addMissing, alignVersions, dryRun: true });
17
+ const operations = [
18
+ ...preview.alignedDeps.map((a) => ({
19
+ type: "update",
20
+ resource: "dependency",
21
+ details: { dep: a.dep, from: a.from, to: a.to, packages: a.packages }
22
+ })),
23
+ ...preview.addedDeps.map((a) => ({
24
+ type: "create",
25
+ resource: "dependency",
26
+ details: { package: a.package, dep: a.dep, version: a.version }
27
+ })),
28
+ ...preview.removedDeps.map((r) => ({
29
+ type: "delete",
30
+ resource: "dependency",
31
+ details: { package: r.package, dep: r.dep }
32
+ }))
33
+ ];
34
+ const total = operations.length;
35
+ return {
36
+ summary: total > 0 ? `Fix ${total} dependency issue(s) across ${preview.packagesScanned} packages` : `No dependency issues found in ${preview.packagesScanned} packages`,
37
+ operations
38
+ };
39
+ },
10
40
  async execute(ctx, input) {
11
41
  const { ui, platform } = ctx;
12
- const flags = "flags" in input && typeof input.flags === "object" && input.flags !== null ? input.flags : input;
13
- const dryRun = flags["dry-run"] ?? false;
42
+ const flags = input.flags;
14
43
  const removeUnused = flags["remove-unused"] || flags.all;
15
44
  const addMissing = flags["add-missing"] || flags.all;
16
45
  const alignVersions = flags["align-versions"] || flags.all;
@@ -21,52 +50,45 @@ var fix_deps_default = defineCommand({
21
50
  return { exitCode: 0 };
22
51
  }
23
52
  if (!removeUnused && !addMissing && !alignVersions) {
24
- ui?.error?.("No fix options specified. Use --remove-unused, --add-missing, --align-versions, or --all");
53
+ validationError(ctx, "No fix options specified", "Use --remove-unused, --add-missing, --align-versions, or --all", flags.json);
25
54
  return { exitCode: 1 };
26
55
  }
27
56
  const loader = useLoader("Scanning packages...");
28
57
  loader.start();
29
- const result = {
30
- packagesScanned: 0,
31
- removedDeps: [],
32
- addedDeps: [],
33
- alignedDeps: [],
34
- dryRun
35
- };
36
58
  const packages = findPackages(ctx.cwd);
37
- result.packagesScanned = packages.length;
38
59
  loader.update({ text: `Scanned ${packages.length} packages` });
39
- if (alignVersions) {
40
- loader.update({ text: "Analyzing duplicate dependencies..." });
41
- const aligned = await alignDuplicateVersions(packages, dryRun);
42
- result.alignedDeps = aligned;
43
- }
44
- if (addMissing) {
45
- loader.update({ text: "Checking for missing workspace dependencies..." });
46
- const added = await addMissingWorkspaceDeps(packages, dryRun);
47
- result.addedDeps = added;
48
- }
49
- if (removeUnused) {
50
- loader.update({ text: "Checking for unused dependencies..." });
51
- const removed = await removeUnusedDeps(packages, dryRun);
52
- result.removedDeps = removed;
53
- }
60
+ const result = await runAnalysis(packages, { removeUnused, addMissing, alignVersions, dryRun: false });
54
61
  loader.succeed("Dependency analysis completed");
55
62
  await platform.analytics.track("quality:fix-deps", {
56
- dryRun,
63
+ dryRun: false,
57
64
  packagesScanned: result.packagesScanned,
58
65
  removedCount: result.removedDeps.length,
59
66
  addedCount: result.addedDeps.length,
60
67
  alignedCount: result.alignedDeps.length
61
68
  });
62
- outputResults(result, flags, ui);
63
- return {
64
- exitCode: 0,
65
- result
66
- };
69
+ outputResults({ ...result, dryRun: false }, flags, ui);
70
+ return { exitCode: 0, result };
67
71
  }
68
72
  }
69
73
  });
74
+ async function runAnalysis(packages, opts) {
75
+ const result = {
76
+ packagesScanned: packages.length,
77
+ removedDeps: [],
78
+ addedDeps: [],
79
+ alignedDeps: []
80
+ };
81
+ if (opts.alignVersions) {
82
+ result.alignedDeps = await alignDuplicateVersions(packages, opts.dryRun);
83
+ }
84
+ if (opts.addMissing) {
85
+ result.addedDeps = await addMissingWorkspaceDeps(packages, opts.dryRun);
86
+ }
87
+ if (opts.removeUnused) {
88
+ result.removedDeps = await removeUnusedDeps(packages, opts.dryRun);
89
+ }
90
+ return result;
91
+ }
70
92
  function findPackages(rootDir) {
71
93
  const packages = [];
72
94
  if (!fs.existsSync(rootDir)) {
@@ -161,12 +183,7 @@ async function alignDuplicateVersions(packages, dryRun) {
161
183
  continue;
162
184
  }
163
185
  for (const { version, packages: pkgNames } of versionCounts.slice(1)) {
164
- aligned.push({
165
- dep,
166
- from: version,
167
- to: targetVersion,
168
- packages: pkgNames
169
- });
186
+ aligned.push({ dep, from: version, to: targetVersion, packages: pkgNames });
170
187
  if (!dryRun) {
171
188
  for (const pkgName of pkgNames) {
172
189
  const pkg = packages.find((p) => p.name === pkgName);
@@ -207,11 +224,7 @@ async function addMissingWorkspaceDeps(packages, dryRun) {
207
224
  };
208
225
  for (const imp of imports) {
209
226
  if (imp.startsWith("@kb-labs/") && workspacePackages.has(imp) && !currentDeps[imp]) {
210
- added.push({
211
- package: pkg.name,
212
- dep: imp,
213
- version: "workspace:*"
214
- });
227
+ added.push({ package: pkg.name, dep: imp, version: "workspace:*" });
215
228
  if (!dryRun) {
216
229
  if (!pkg.json.dependencies) {
217
230
  pkg.json.dependencies = {};
@@ -242,10 +255,7 @@ async function removeUnusedDeps(packages, dryRun) {
242
255
  continue;
243
256
  }
244
257
  if (!imports.has(dep)) {
245
- removed.push({
246
- package: pkg.name,
247
- dep
248
- });
258
+ removed.push({ package: pkg.name, dep });
249
259
  if (!dryRun) {
250
260
  delete pkg.json.dependencies?.[dep];
251
261
  delete pkg.json.devDependencies?.[dep];
@@ -321,7 +331,7 @@ function outputStats(stats, flags, ui) {
321
331
  });
322
332
  }
323
333
  ui?.success?.("Dependency statistics", {
324
- title: "\u{1F4CA} Dependency Statistics",
334
+ title: "Dependency Statistics",
325
335
  sections
326
336
  });
327
337
  }
@@ -331,55 +341,36 @@ function outputResults(result, flags, ui) {
331
341
  return;
332
342
  }
333
343
  const sections = [];
334
- const summaryItems = [
335
- `Packages Scanned: ${result.packagesScanned}`,
336
- `Mode: ${result.dryRun ? "\u{1F50D} Dry Run (no changes applied)" : "\u2705 Changes Applied"}`
337
- ];
338
- sections.push({ header: "Summary", items: summaryItems });
344
+ sections.push({
345
+ header: "Summary",
346
+ items: [`Packages Scanned: ${result.packagesScanned}`]
347
+ });
339
348
  if (result.alignedDeps.length > 0) {
340
- const alignedItems = result.alignedDeps.map(
341
- (a) => `${a.dep}: ${a.from} \u2192 ${a.to} (${a.packages.length} packages)`
342
- );
343
- sections.push({ header: "Aligned Versions", items: alignedItems.slice(0, 10) });
349
+ const items = result.alignedDeps.slice(0, 10).map((a) => `${a.dep}: ${a.from} \u2192 ${a.to} (${a.packages.length} packages)`);
344
350
  if (result.alignedDeps.length > 10) {
345
- const lastSection = sections[sections.length - 1];
346
- if (lastSection) {
347
- lastSection.items.push(`... and ${result.alignedDeps.length - 10} more`);
348
- }
351
+ items.push(`... and ${result.alignedDeps.length - 10} more`);
349
352
  }
353
+ sections.push({ header: "Aligned Versions", items });
350
354
  }
351
355
  if (result.addedDeps.length > 0) {
352
- const addedItems = result.addedDeps.map((a) => `${a.package}: +${a.dep}`);
353
- sections.push({ header: "Added Dependencies", items: addedItems.slice(0, 10) });
356
+ const items = result.addedDeps.slice(0, 10).map((a) => `${a.package}: +${a.dep}`);
354
357
  if (result.addedDeps.length > 10) {
355
- const lastSection = sections[sections.length - 1];
356
- if (lastSection) {
357
- lastSection.items.push(`... and ${result.addedDeps.length - 10} more`);
358
- }
358
+ items.push(`... and ${result.addedDeps.length - 10} more`);
359
359
  }
360
+ sections.push({ header: "Added Dependencies", items });
360
361
  }
361
362
  if (result.removedDeps.length > 0) {
362
- const removedItems = result.removedDeps.map((r) => `${r.package}: -${r.dep}`);
363
- sections.push({ header: "Removed Dependencies", items: removedItems.slice(0, 10) });
363
+ const items = result.removedDeps.slice(0, 10).map((r) => `${r.package}: -${r.dep}`);
364
364
  if (result.removedDeps.length > 10) {
365
- const lastSection = sections[sections.length - 1];
366
- if (lastSection) {
367
- lastSection.items.push(`... and ${result.removedDeps.length - 10} more`);
368
- }
365
+ items.push(`... and ${result.removedDeps.length - 10} more`);
369
366
  }
367
+ sections.push({ header: "Removed Dependencies", items });
370
368
  }
371
369
  if (result.alignedDeps.length === 0 && result.addedDeps.length === 0 && result.removedDeps.length === 0) {
372
- sections.push({ header: "Result", items: ["\u2705 No issues found!"] });
373
- }
374
- if (result.dryRun && (result.alignedDeps.length > 0 || result.addedDeps.length > 0 || result.removedDeps.length > 0)) {
375
- sections.push({
376
- header: "Next Steps",
377
- items: ["Remove --dry-run flag to apply changes", "Run `pnpm install` after applying changes"]
378
- });
370
+ sections.push({ header: "Result", items: ["No issues found!"] });
379
371
  }
380
- const title = result.dryRun ? "\u{1F50D} Dependency Fix Preview" : "\u2705 Dependency Fix Complete";
381
372
  ui?.success?.("Dependency fix completed", {
382
- title,
373
+ title: "Dependency Fix Complete",
383
374
  sections
384
375
  });
385
376
  }