@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.
- package/README.md +120 -0
- package/dist/cli/commands/build-order.d.ts +16 -0
- package/dist/cli/commands/build-order.js +113 -0
- package/dist/cli/commands/build-order.js.map +1 -0
- package/dist/cli/commands/check-builds.d.ts +10 -0
- package/dist/cli/commands/check-builds.js +93 -0
- package/dist/cli/commands/check-builds.js.map +1 -0
- package/dist/cli/commands/check-tests.d.ts +10 -0
- package/dist/cli/commands/check-tests.js +114 -0
- package/dist/cli/commands/check-tests.js.map +1 -0
- package/dist/cli/commands/check-types.d.ts +10 -0
- package/dist/cli/commands/check-types.js +108 -0
- package/dist/cli/commands/check-types.js.map +1 -0
- package/dist/cli/commands/cycles.d.ts +17 -0
- package/dist/cli/commands/cycles.js +85 -0
- package/dist/cli/commands/cycles.js.map +1 -0
- package/dist/cli/commands/dead-code.d.ts +10 -0
- package/dist/cli/commands/dead-code.js +217 -0
- package/dist/cli/commands/dead-code.js.map +1 -0
- package/dist/cli/commands/fix-deps.d.ts +28 -0
- package/dist/cli/commands/fix-deps.js +389 -0
- package/dist/cli/commands/fix-deps.js.map +1 -0
- package/dist/cli/commands/flags.d.ts +344 -0
- package/dist/cli/commands/flags.js +298 -0
- package/dist/cli/commands/flags.js.map +1 -0
- package/dist/cli/commands/health.d.ts +10 -0
- package/dist/cli/commands/health.js +210 -0
- package/dist/cli/commands/health.js.map +1 -0
- package/dist/cli/commands/index.d.ts +14 -0
- package/dist/cli/commands/index.js +1747 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/stats.d.ts +10 -0
- package/dist/cli/commands/stats.js +282 -0
- package/dist/cli/commands/stats.js.map +1 -0
- package/dist/cli/commands/visualize.d.ts +26 -0
- package/dist/cli/commands/visualize.js +210 -0
- package/dist/cli/commands/visualize.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +666 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +168 -0
- package/dist/manifest.js +666 -0
- package/dist/manifest.js.map +1 -0
- package/dist/rest/handlers/build-order-handler.d.ts +17 -0
- package/dist/rest/handlers/build-order-handler.js +28 -0
- package/dist/rest/handlers/build-order-handler.js.map +1 -0
- package/dist/rest/handlers/builds-handler.d.ts +12 -0
- package/dist/rest/handlers/builds-handler.js +17 -0
- package/dist/rest/handlers/builds-handler.js.map +1 -0
- package/dist/rest/handlers/cycles-handler.d.ts +13 -0
- package/dist/rest/handlers/cycles-handler.js +23 -0
- package/dist/rest/handlers/cycles-handler.js.map +1 -0
- package/dist/rest/handlers/dependencies-handler.d.ts +14 -0
- package/dist/rest/handlers/dependencies-handler.js +19 -0
- package/dist/rest/handlers/dependencies-handler.js.map +1 -0
- package/dist/rest/handlers/graph-handler.d.ts +30 -0
- package/dist/rest/handlers/graph-handler.js +155 -0
- package/dist/rest/handlers/graph-handler.js.map +1 -0
- package/dist/rest/handlers/health-handler.d.ts +15 -0
- package/dist/rest/handlers/health-handler.js +19 -0
- package/dist/rest/handlers/health-handler.js.map +1 -0
- package/dist/rest/handlers/stale-handler.d.ts +30 -0
- package/dist/rest/handlers/stale-handler.js +20 -0
- package/dist/rest/handlers/stale-handler.js.map +1 -0
- package/dist/rest/handlers/stats-handler.d.ts +16 -0
- package/dist/rest/handlers/stats-handler.js +29 -0
- package/dist/rest/handlers/stats-handler.js.map +1 -0
- package/dist/rest/handlers/tests-handler.d.ts +14 -0
- package/dist/rest/handlers/tests-handler.js +19 -0
- package/dist/rest/handlers/tests-handler.js.map +1 -0
- package/dist/rest/handlers/types-handler.d.ts +12 -0
- package/dist/rest/handlers/types-handler.js +17 -0
- package/dist/rest/handlers/types-handler.js.map +1 -0
- package/package.json +98 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { defineCommand } from '@kb-labs/sdk';
|
|
2
|
+
import { CACHE_KEYS } from '@kb-labs/quality-contracts';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
// src/cli/commands/health.ts
|
|
7
|
+
var health_default = defineCommand({
|
|
8
|
+
id: "quality:health",
|
|
9
|
+
description: "Check monorepo health score",
|
|
10
|
+
handler: {
|
|
11
|
+
async execute(ctx, input) {
|
|
12
|
+
const { ui, platform } = ctx;
|
|
13
|
+
const flags = input.flags ?? input;
|
|
14
|
+
const health = await calculateHealth(ctx.cwd, flags.package);
|
|
15
|
+
await platform.cache.set(CACHE_KEYS.HEALTH, health, 5 * 60 * 1e3);
|
|
16
|
+
await platform.analytics.track("quality:health", {
|
|
17
|
+
score: health.score,
|
|
18
|
+
grade: health.grade,
|
|
19
|
+
issueCount: health.issues.length,
|
|
20
|
+
packageSpecific: !!flags.package
|
|
21
|
+
});
|
|
22
|
+
outputHealth(health, flags, ui);
|
|
23
|
+
return {
|
|
24
|
+
exitCode: health.score < 60 ? 1 : 0,
|
|
25
|
+
result: health
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
function findPackages(rootDir, specificPackage) {
|
|
31
|
+
const packages = [];
|
|
32
|
+
if (!fs.existsSync(rootDir)) {
|
|
33
|
+
return packages;
|
|
34
|
+
}
|
|
35
|
+
const entries = fs.readdirSync(rootDir, { withFileTypes: true });
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
if (!entry.isDirectory() || !entry.name.startsWith("kb-labs-")) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const repoPath = path.join(rootDir, entry.name);
|
|
41
|
+
const packagesDir = path.join(repoPath, "packages");
|
|
42
|
+
if (!fs.existsSync(packagesDir)) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const packageDirs = fs.readdirSync(packagesDir, { withFileTypes: true });
|
|
46
|
+
for (const pkgDir of packageDirs) {
|
|
47
|
+
if (!pkgDir.isDirectory()) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const packageJsonPath = path.join(packagesDir, pkgDir.name, "package.json");
|
|
51
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
52
|
+
if (specificPackage) {
|
|
53
|
+
const pkgJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
54
|
+
if (pkgJson.name === specificPackage || pkgDir.name === specificPackage) {
|
|
55
|
+
packages.push(packageJsonPath);
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
packages.push(packageJsonPath);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return packages;
|
|
64
|
+
}
|
|
65
|
+
async function calculateHealth(rootDir, specificPackage) {
|
|
66
|
+
const packages = findPackages(rootDir, specificPackage);
|
|
67
|
+
let score = 100;
|
|
68
|
+
const issues = [];
|
|
69
|
+
const allDeps = /* @__PURE__ */ new Map();
|
|
70
|
+
for (const packagePath of packages) {
|
|
71
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
|
|
72
|
+
const deps = {
|
|
73
|
+
...packageJson.dependencies,
|
|
74
|
+
...packageJson.devDependencies
|
|
75
|
+
};
|
|
76
|
+
for (const [dep, version] of Object.entries(deps)) {
|
|
77
|
+
if (!allDeps.has(dep)) {
|
|
78
|
+
allDeps.set(dep, /* @__PURE__ */ new Set());
|
|
79
|
+
}
|
|
80
|
+
allDeps.get(dep).add(version);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const duplicates = Array.from(allDeps.entries()).filter(([_, versions]) => versions.size > 1).filter(([dep]) => !dep.startsWith("@kb-labs/"));
|
|
84
|
+
if (duplicates.length > 0) {
|
|
85
|
+
const penalty = Math.min(20, duplicates.length * 2);
|
|
86
|
+
score -= penalty;
|
|
87
|
+
issues.push({
|
|
88
|
+
type: "duplicates",
|
|
89
|
+
severity: "warning",
|
|
90
|
+
message: `${duplicates.length} duplicate dependencies (-${penalty})`
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
let missingReadmes = 0;
|
|
94
|
+
for (const packagePath of packages) {
|
|
95
|
+
const packageDir = path.dirname(packagePath);
|
|
96
|
+
const readmePath = path.join(packageDir, "README.md");
|
|
97
|
+
if (!fs.existsSync(readmePath)) {
|
|
98
|
+
missingReadmes++;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (missingReadmes > 0) {
|
|
102
|
+
const penalty = Math.min(15, missingReadmes);
|
|
103
|
+
score -= penalty;
|
|
104
|
+
issues.push({
|
|
105
|
+
type: "missing-docs",
|
|
106
|
+
severity: "warning",
|
|
107
|
+
message: `${missingReadmes} packages missing README (-${penalty})`
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
let namingIssues = 0;
|
|
111
|
+
for (const packagePath of packages) {
|
|
112
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
|
|
113
|
+
const packageName = packageJson.name;
|
|
114
|
+
if (!packageName || !packageName.startsWith("@kb-labs/")) {
|
|
115
|
+
namingIssues++;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (namingIssues > 0) {
|
|
119
|
+
const penalty = Math.min(10, namingIssues * 2);
|
|
120
|
+
score -= penalty;
|
|
121
|
+
issues.push({
|
|
122
|
+
type: "naming",
|
|
123
|
+
severity: "critical",
|
|
124
|
+
message: `${namingIssues} packages with naming issues (-${penalty})`
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
let missingTypes = 0;
|
|
128
|
+
for (const packagePath of packages) {
|
|
129
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
|
|
130
|
+
if (!packageJson.types && !packageJson.typings) {
|
|
131
|
+
missingTypes++;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (missingTypes > 0) {
|
|
135
|
+
const penalty = Math.min(15, missingTypes);
|
|
136
|
+
score -= penalty;
|
|
137
|
+
issues.push({
|
|
138
|
+
type: "missing-types",
|
|
139
|
+
severity: "warning",
|
|
140
|
+
message: `${missingTypes} packages missing types field (-${penalty})`
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
let grade;
|
|
144
|
+
if (score >= 90) {
|
|
145
|
+
grade = "A";
|
|
146
|
+
} else if (score >= 80) {
|
|
147
|
+
grade = "B";
|
|
148
|
+
} else if (score >= 70) {
|
|
149
|
+
grade = "C";
|
|
150
|
+
} else if (score >= 60) {
|
|
151
|
+
grade = "D";
|
|
152
|
+
} else {
|
|
153
|
+
grade = "F";
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
score: Math.max(0, score),
|
|
157
|
+
grade,
|
|
158
|
+
issues
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function outputHealth(health, flags, ui) {
|
|
162
|
+
if (flags.json) {
|
|
163
|
+
ui?.json?.(health);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const sections = [];
|
|
167
|
+
const scoreItems = [
|
|
168
|
+
`Score: ${health.score}/100`,
|
|
169
|
+
`Grade: ${health.grade}`,
|
|
170
|
+
`Status: ${health.score >= 80 ? "\u2705 Healthy" : health.score >= 60 ? "\u26A0\uFE0F Needs Attention" : "\u274C Critical"}`
|
|
171
|
+
];
|
|
172
|
+
sections.push({ header: "Health Score", items: scoreItems });
|
|
173
|
+
if (health.issues.length > 0) {
|
|
174
|
+
const issueItems = health.issues.map((issue) => {
|
|
175
|
+
const icon = issue.severity === "critical" ? "\u274C" : issue.severity === "warning" ? "\u26A0\uFE0F" : "\u2139\uFE0F";
|
|
176
|
+
return `${icon} ${issue.message}`;
|
|
177
|
+
});
|
|
178
|
+
if (flags.detailed) {
|
|
179
|
+
sections.push({ header: "Issues (Detailed)", items: issueItems });
|
|
180
|
+
} else {
|
|
181
|
+
sections.push({ header: "Issues", items: issueItems });
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
sections.push({ header: "Issues", items: ["\u2705 No issues found!"] });
|
|
185
|
+
}
|
|
186
|
+
if (health.score < 90) {
|
|
187
|
+
const recommendations = [];
|
|
188
|
+
if (health.issues.some((i) => i.type === "duplicates")) {
|
|
189
|
+
recommendations.push("Run `kb quality:fix-deps --align-versions` to fix duplicates");
|
|
190
|
+
}
|
|
191
|
+
if (health.issues.some((i) => i.type === "missing-docs")) {
|
|
192
|
+
recommendations.push("Add README.md files to packages");
|
|
193
|
+
}
|
|
194
|
+
if (health.issues.some((i) => i.type === "missing-types")) {
|
|
195
|
+
recommendations.push('Add "types" field to package.json or enable dts generation');
|
|
196
|
+
}
|
|
197
|
+
if (recommendations.length > 0) {
|
|
198
|
+
sections.push({ header: "Recommendations", items: recommendations });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
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";
|
|
202
|
+
ui?.success?.("Health check completed", {
|
|
203
|
+
title,
|
|
204
|
+
sections
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export { health_default as default };
|
|
209
|
+
//# sourceMappingURL=health.js.map
|
|
210
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/cli/commands/health.ts"],"names":[],"mappings":"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","file":"health.js","sourcesContent":["/**\n * quality:health - Monorepo health check\n *\n * Analyzes monorepo health including:\n * - Dependency issues (duplicates, unused, missing)\n * - Structure problems (missing READMEs, inconsistent naming)\n * - Build health (TypeScript errors, missing types)\n * - Overall health score with grade (A-F)\n */\n\nimport { defineCommand, type PluginContextV3 } from '@kb-labs/sdk';\nimport type { HealthScore, Issue } from '@kb-labs/quality-contracts';\nimport { CACHE_KEYS } from '@kb-labs/quality-contracts';\nimport type { HealthFlags } from './flags.js';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n// Input type with backward compatibility\ntype HealthInput = HealthFlags & { argv?: string[] };\n\ntype HealthCommandResult = {\n exitCode: number;\n result?: HealthScore;\n meta?: Record<string, unknown>;\n};\n\nexport default defineCommand({\n id: 'quality:health',\n description: 'Check monorepo health score',\n\n handler: {\n async execute(ctx: PluginContextV3, input: HealthInput): Promise<HealthCommandResult> {\n const { ui, platform } = ctx;\n\n // V3: Flags come in input.flags object (not auto-merged)\n const flags = (input as any).flags ?? input;\n\n // Calculate health score\n const health = await calculateHealth(ctx.cwd, flags.package);\n\n // Cache results for 5 minutes\n await platform.cache.set(CACHE_KEYS.HEALTH, health, 5 * 60 * 1000);\n\n // Track analytics\n await platform.analytics.track('quality:health', {\n score: health.score,\n grade: health.grade,\n issueCount: health.issues.length,\n packageSpecific: !!flags.package,\n });\n\n // Output results\n outputHealth(health, flags, ui);\n\n return {\n exitCode: health.score < 60 ? 1 : 0,\n result: health,\n };\n },\n },\n});\n\n/**\n * Find all packages in the monorepo\n */\nfunction findPackages(rootDir: string, specificPackage?: string): string[] {\n const packages: string[] = [];\n\n if (!fs.existsSync(rootDir)) {\n return packages;\n }\n\n const entries = fs.readdirSync(rootDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.startsWith('kb-labs-')) {continue;}\n\n const repoPath = path.join(rootDir, entry.name);\n const packagesDir = path.join(repoPath, 'packages');\n\n if (!fs.existsSync(packagesDir)) {continue;}\n\n const packageDirs = fs.readdirSync(packagesDir, { withFileTypes: true });\n\n for (const pkgDir of packageDirs) {\n if (!pkgDir.isDirectory()) {continue;}\n\n const packageJsonPath = path.join(packagesDir, pkgDir.name, 'package.json');\n\n if (fs.existsSync(packageJsonPath)) {\n // If specific package requested, filter\n if (specificPackage) {\n const pkgJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));\n if (pkgJson.name === specificPackage || pkgDir.name === specificPackage) {\n packages.push(packageJsonPath);\n }\n } else {\n packages.push(packageJsonPath);\n }\n }\n }\n }\n\n return packages;\n}\n\n/**\n * Calculate comprehensive health score\n */\nasync function calculateHealth(rootDir: string, specificPackage?: string): Promise<HealthScore> {\n const packages = findPackages(rootDir, specificPackage);\n let score = 100;\n const issues: Issue[] = [];\n\n // 1. Check for duplicate dependencies\n const allDeps = new Map<string, Set<string>>();\n\n for (const packagePath of packages) {\n const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));\n const deps = {\n ...packageJson.dependencies,\n ...packageJson.devDependencies,\n };\n\n for (const [dep, version] of Object.entries(deps)) {\n if (!allDeps.has(dep)) {\n allDeps.set(dep, new Set());\n }\n allDeps.get(dep)!.add(version as string);\n }\n }\n\n const duplicates = Array.from(allDeps.entries())\n .filter(([_, versions]) => versions.size > 1)\n .filter(([dep]) => !dep.startsWith('@kb-labs/')); // Ignore workspace deps\n\n if (duplicates.length > 0) {\n const penalty = Math.min(20, duplicates.length * 2);\n score -= penalty;\n issues.push({\n type: 'duplicates',\n severity: 'warning',\n message: `${duplicates.length} duplicate dependencies (-${penalty})`,\n });\n }\n\n // 2. Check for missing READMEs\n let missingReadmes = 0;\n for (const packagePath of packages) {\n const packageDir = path.dirname(packagePath);\n const readmePath = path.join(packageDir, 'README.md');\n if (!fs.existsSync(readmePath)) {\n missingReadmes++;\n }\n }\n\n if (missingReadmes > 0) {\n const penalty = Math.min(15, missingReadmes);\n score -= penalty;\n issues.push({\n type: 'missing-docs',\n severity: 'warning',\n message: `${missingReadmes} packages missing README (-${penalty})`,\n });\n }\n\n // 3. Check for inconsistent naming\n let namingIssues = 0;\n for (const packagePath of packages) {\n const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));\n const packageName = packageJson.name;\n\n if (!packageName || !packageName.startsWith('@kb-labs/')) {\n namingIssues++;\n }\n }\n\n if (namingIssues > 0) {\n const penalty = Math.min(10, namingIssues * 2);\n score -= penalty;\n issues.push({\n type: 'naming',\n severity: 'critical',\n message: `${namingIssues} packages with naming issues (-${penalty})`,\n });\n }\n\n // 4. Check for missing type definitions\n let missingTypes = 0;\n for (const packagePath of packages) {\n const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));\n\n if (!packageJson.types && !packageJson.typings) {\n missingTypes++;\n }\n }\n\n if (missingTypes > 0) {\n const penalty = Math.min(15, missingTypes);\n score -= penalty;\n issues.push({\n type: 'missing-types',\n severity: 'warning',\n message: `${missingTypes} packages missing types field (-${penalty})`,\n });\n }\n\n // Determine grade\n let grade: 'A' | 'B' | 'C' | 'D' | 'F';\n if (score >= 90) {grade = 'A';}\n else if (score >= 80) {grade = 'B';}\n else if (score >= 70) {grade = 'C';}\n else if (score >= 60) {grade = 'D';}\n else {grade = 'F';}\n\n return {\n score: Math.max(0, score),\n grade,\n issues,\n };\n}\n\n/**\n * Output health check results\n */\nfunction outputHealth(health: HealthScore, flags: any, ui: any) {\n if (flags.json) {\n ui?.json?.(health);\n return;\n }\n\n // Build sections\n const sections: Array<{ header: string; items: string[] }> = [];\n\n // Score section\n const scoreItems = [\n `Score: ${health.score}/100`,\n `Grade: ${health.grade}`,\n `Status: ${health.score >= 80 ? '✅ Healthy' : health.score >= 60 ? '⚠️ Needs Attention' : '❌ Critical'}`,\n ];\n sections.push({ header: 'Health Score', items: scoreItems });\n\n // Issues section\n if (health.issues.length > 0) {\n const issueItems = health.issues.map(issue => {\n const icon = issue.severity === 'critical' ? '❌' : issue.severity === 'warning' ? '⚠️' : 'ℹ️';\n return `${icon} ${issue.message}`;\n });\n\n if (flags.detailed) {\n sections.push({ header: 'Issues (Detailed)', items: issueItems });\n } else {\n sections.push({ header: 'Issues', items: issueItems });\n }\n } else {\n sections.push({ header: 'Issues', items: ['✅ No issues found!'] });\n }\n\n // Recommendations\n if (health.score < 90) {\n const recommendations: string[] = [];\n\n if (health.issues.some(i => i.type === 'duplicates')) {\n recommendations.push('Run `kb quality:fix-deps --align-versions` to fix duplicates');\n }\n if (health.issues.some(i => i.type === 'missing-docs')) {\n recommendations.push('Add README.md files to packages');\n }\n if (health.issues.some(i => i.type === 'missing-types')) {\n recommendations.push('Add \"types\" field to package.json or enable dts generation');\n }\n\n if (recommendations.length > 0) {\n sections.push({ header: 'Recommendations', items: recommendations });\n }\n }\n\n const title = health.score >= 80\n ? '💚 Monorepo Health Check - Healthy'\n : health.score >= 60\n ? '💛 Monorepo Health Check - Needs Attention'\n : '❤️ Monorepo Health Check - Critical';\n\n ui?.success?.('Health check completed', {\n title,\n sections,\n });\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { default as buildOrder } from './build-order.js';
|
|
2
|
+
export { default as checkBuilds } from './check-builds.js';
|
|
3
|
+
export { default as checkTests } from './check-tests.js';
|
|
4
|
+
export { default as checkTypes } from './check-types.js';
|
|
5
|
+
export { default as cycles } from './cycles.js';
|
|
6
|
+
export { default as deadCode } from './dead-code.js';
|
|
7
|
+
export { default as fixDeps } from './fix-deps.js';
|
|
8
|
+
export { default as health } from './health.js';
|
|
9
|
+
export { default as stats } from './stats.js';
|
|
10
|
+
export { default as visualize } from './visualize.js';
|
|
11
|
+
import '@kb-labs/shared-command-kit';
|
|
12
|
+
import '@kb-labs/quality-core/graph';
|
|
13
|
+
import '@kb-labs/quality-contracts';
|
|
14
|
+
import './flags.js';
|