@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.
- package/dist/cli/commands/build-order.d.ts +5 -12
- package/dist/cli/commands/build-order.js +45 -97
- package/dist/cli/commands/build-order.js.map +1 -1
- package/dist/cli/commands/check-layers.d.ts +9 -0
- package/dist/cli/commands/check-layers.js +48 -0
- package/dist/cli/commands/check-layers.js.map +1 -0
- package/dist/cli/commands/context.d.ts +9 -0
- package/dist/cli/commands/context.js +94 -0
- package/dist/cli/commands/context.js.map +1 -0
- package/dist/cli/commands/coupling.d.ts +9 -0
- package/dist/cli/commands/coupling.js +35 -0
- package/dist/cli/commands/coupling.js.map +1 -0
- package/dist/cli/commands/dead-code.d.ts +4 -5
- package/dist/cli/commands/dead-code.js +41 -197
- package/dist/cli/commands/dead-code.js.map +1 -1
- package/dist/cli/commands/fix-deps.d.ts +2 -5
- package/dist/cli/commands/fix-deps.js +74 -83
- package/dist/cli/commands/fix-deps.js.map +1 -1
- package/dist/cli/commands/flags.d.ts +115 -1
- package/dist/cli/commands/flags.js +46 -1
- package/dist/cli/commands/flags.js.map +1 -1
- package/dist/cli/commands/gate.d.ts +9 -0
- package/dist/cli/commands/gate.js +59 -0
- package/dist/cli/commands/gate.js.map +1 -0
- package/dist/cli/commands/health.d.ts +4 -5
- package/dist/cli/commands/health.js +42 -191
- package/dist/cli/commands/health.js.map +1 -1
- package/dist/cli/commands/history.d.ts +9 -0
- package/dist/cli/commands/history.js +55 -0
- package/dist/cli/commands/history.js.map +1 -0
- package/dist/cli/commands/index.d.ts +6 -2
- package/dist/cli/commands/index.js +411 -828
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/snapshot.d.ts +9 -0
- package/dist/cli/commands/snapshot.js +91 -0
- package/dist/cli/commands/snapshot.js.map +1 -0
- package/dist/cli/commands/stats.d.ts +4 -5
- package/dist/cli/commands/stats.js +17 -265
- package/dist/cli/commands/stats.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +198 -306
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +95 -82
- package/dist/manifest.js +198 -306
- package/dist/manifest.js.map +1 -1
- package/dist/rest/handlers/build-order-handler.d.ts +5 -13
- package/dist/rest/handlers/build-order-handler.js +14 -17
- package/dist/rest/handlers/build-order-handler.js.map +1 -1
- package/dist/rest/handlers/builds-handler.js +10 -6
- package/dist/rest/handlers/builds-handler.js.map +1 -1
- package/dist/rest/handlers/coupling-handler.d.ts +8 -0
- package/dist/rest/handlers/coupling-handler.js +28 -0
- package/dist/rest/handlers/coupling-handler.js.map +1 -0
- package/dist/rest/handlers/cycles-handler.js +16 -12
- package/dist/rest/handlers/cycles-handler.js.map +1 -1
- package/dist/rest/handlers/dependencies-handler.js +12 -8
- package/dist/rest/handlers/dependencies-handler.js.map +1 -1
- package/dist/rest/handlers/graph-handler.js +77 -73
- package/dist/rest/handlers/graph-handler.js.map +1 -1
- package/dist/rest/handlers/health-handler.d.ts +4 -11
- package/dist/rest/handlers/health-handler.js +35 -9
- package/dist/rest/handlers/health-handler.js.map +1 -1
- package/dist/rest/handlers/history-handler.d.ts +7 -0
- package/dist/rest/handlers/history-handler.js +21 -0
- package/dist/rest/handlers/history-handler.js.map +1 -0
- package/dist/rest/handlers/knip-handler.d.ts +8 -0
- package/dist/rest/handlers/knip-handler.js +31 -0
- package/dist/rest/handlers/knip-handler.js.map +1 -0
- package/dist/rest/handlers/layers-handler.d.ts +8 -0
- package/dist/rest/handlers/layers-handler.js +28 -0
- package/dist/rest/handlers/layers-handler.js.map +1 -0
- package/dist/rest/handlers/snapshot-handler.d.ts +7 -0
- package/dist/rest/handlers/snapshot-handler.js +67 -0
- package/dist/rest/handlers/snapshot-handler.js.map +1 -0
- package/dist/rest/handlers/stale-handler.js +13 -9
- package/dist/rest/handlers/stale-handler.js.map +1 -1
- package/dist/rest/handlers/stats-handler.d.ts +4 -9
- package/dist/rest/handlers/stats-handler.js +22 -18
- package/dist/rest/handlers/stats-handler.js.map +1 -1
- package/dist/rest/handlers/tests-handler.js +12 -8
- package/dist/rest/handlers/tests-handler.js.map +1 -1
- package/dist/rest/handlers/types-handler.js +10 -6
- package/dist/rest/handlers/types-handler.js.map +1 -1
- package/dist/widgets/117.js +24 -1
- package/dist/widgets/117.js.map +1 -1
- package/dist/widgets/370.js +1 -1
- package/dist/widgets/370.js.map +1 -1
- package/dist/widgets/62.js +97 -0
- package/dist/widgets/62.js.map +1 -0
- package/dist/widgets/__federation_expose_QualityOverview.js +1 -1
- package/dist/widgets/__federation_expose_QualityOverview.js.map +1 -1
- package/dist/widgets/mf-manifest.json +27 -27
- package/dist/widgets/mf-stats.json +27 -27
- package/dist/widgets/remoteEntry.js +2 -2
- package/dist/widgets/remoteEntry.js.map +1 -1
- package/package.json +9 -8
- package/dist/widgets/418.js +0 -39
- package/dist/widgets/418.js.map +0 -1
|
@@ -1,207 +1,58 @@
|
|
|
1
|
-
import { defineCommand } from '@kb-labs/sdk';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import path from 'path';
|
|
1
|
+
import { defineCommand, useConfig } from '@kb-labs/sdk';
|
|
2
|
+
import { analyzeLayering, analyzeTypes, analyzeCoupling, runKnip, calculateHealth } from '@kb-labs/quality-core';
|
|
3
|
+
import { CACHE_KEYS, defaultQualityConfig, CACHE_TTLS } from '@kb-labs/quality-contracts';
|
|
5
4
|
|
|
6
5
|
// src/cli/commands/health.ts
|
|
7
6
|
var health_default = defineCommand({
|
|
8
7
|
id: "quality:health",
|
|
9
|
-
description: "
|
|
8
|
+
description: "Multidimensional health score: architecture, TypeScript, dead code, dependencies",
|
|
10
9
|
handler: {
|
|
11
10
|
async execute(ctx, input) {
|
|
12
|
-
const {
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
await platform.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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;
|
|
11
|
+
const { flags } = input;
|
|
12
|
+
const config = await useConfig();
|
|
13
|
+
const cwd = ctx.cwd ?? process.cwd();
|
|
14
|
+
const cfg = config ?? defaultQualityConfig;
|
|
15
|
+
const cached = await ctx.platform.cache.get(CACHE_KEYS.HEALTH);
|
|
16
|
+
if (cached && !flags.refresh) {
|
|
17
|
+
const health2 = cached;
|
|
18
|
+
outputHealth(health2, flags, ctx);
|
|
19
|
+
return { exitCode: health2.score < cfg.thresholds.health ? 1 : 0 };
|
|
49
20
|
}
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
packages.push(packageJsonPath);
|
|
59
|
-
}
|
|
21
|
+
const [layering, types] = await Promise.all([
|
|
22
|
+
analyzeLayering({ rootDir: cwd, layerMap: cfg.layers }),
|
|
23
|
+
analyzeTypes(cwd)
|
|
24
|
+
]);
|
|
25
|
+
const coupling = analyzeCoupling({ rootDir: cwd, layerMap: cfg.layers });
|
|
26
|
+
let knip = { unusedFiles: [], unusedExports: [], unusedDependencies: [], unlistedDependencies: [], totalIssues: 0 };
|
|
27
|
+
if (cfg.knip.enabled) {
|
|
28
|
+
knip = await runKnip({ shell: ctx.api.shell, rootDir: cwd });
|
|
60
29
|
}
|
|
30
|
+
const health = calculateHealth({
|
|
31
|
+
layering,
|
|
32
|
+
coupling,
|
|
33
|
+
types,
|
|
34
|
+
knip,
|
|
35
|
+
totalPackages: coupling.packages.length,
|
|
36
|
+
avgTestCoverage: null,
|
|
37
|
+
thresholds: cfg.thresholds
|
|
38
|
+
});
|
|
39
|
+
await ctx.platform.cache.set(CACHE_KEYS.HEALTH, health, CACHE_TTLS.FAST);
|
|
40
|
+
outputHealth(health, flags, ctx);
|
|
41
|
+
return { exitCode: health.score < cfg.thresholds.health ? 1 : 0 };
|
|
61
42
|
}
|
|
62
43
|
}
|
|
63
|
-
|
|
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) {
|
|
44
|
+
});
|
|
45
|
+
function outputHealth(health, flags, ctx) {
|
|
162
46
|
if (flags.json) {
|
|
163
|
-
ui?.json?.(health);
|
|
47
|
+
ctx.ui?.json?.(health);
|
|
164
48
|
return;
|
|
165
49
|
}
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
|
50
|
+
const dimItems = Object.entries(health.dimensions).map(([key, dim]) => {
|
|
51
|
+
const details = dim.details.length > 0 ? `: ${dim.details.join(", ")}` : "";
|
|
52
|
+
return `${key} ${dim.score}/100 (${dim.grade})${details}`;
|
|
53
|
+
});
|
|
54
|
+
ctx.ui?.success?.(`Health score: ${health.score}/100 (${health.grade})`, {
|
|
55
|
+
sections: [{ header: "Dimensions", items: dimItems }]
|
|
205
56
|
});
|
|
206
57
|
}
|
|
207
58
|
|
|
@@ -1 +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 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: HealthInput }).flags\n : 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: Record<string, unknown>, ui: PluginContextV3['ui']) {\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"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/cli/commands/health.ts"],"names":["health"],"mappings":"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","file":"health.js","sourcesContent":["import {\n defineCommand,\n useConfig,\n type CLIInput,\n type PluginContextV3,\n} from '@kb-labs/sdk';\nimport {\n analyzeLayering,\n analyzeCoupling,\n runKnip,\n analyzeTypes,\n calculateHealth,\n} from '@kb-labs/quality-core';\nimport { CACHE_KEYS, CACHE_TTLS, defaultQualityConfig, type QualityPluginConfig, type HealthScore, type KnipReport } from '@kb-labs/quality-contracts';\nimport type { HealthFlags } from './flags.js';\n\nexport default defineCommand<unknown, CLIInput<HealthFlags>, { exitCode: number }>({\n id: 'quality:health',\n description: 'Multidimensional health score: architecture, TypeScript, dead code, dependencies',\n\n handler: {\n async execute(ctx: PluginContextV3, input: CLIInput<HealthFlags>): 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 const cached = await ctx.platform.cache.get(CACHE_KEYS.HEALTH);\n if (cached && !flags.refresh) {\n const health = cached as HealthScore;\n outputHealth(health, flags, ctx);\n return { exitCode: health.score < cfg.thresholds.health ? 1 : 0 };\n }\n\n const [layering, types] = await Promise.all([\n analyzeLayering({ rootDir: cwd, layerMap: cfg.layers }),\n analyzeTypes(cwd),\n ]);\n\n const coupling = analyzeCoupling({ rootDir: cwd, layerMap: cfg.layers });\n\n let knip: KnipReport = { unusedFiles: [], unusedExports: [], unusedDependencies: [], unlistedDependencies: [], totalIssues: 0 };\n if (cfg.knip.enabled) {\n knip = await runKnip({ shell: ctx.api.shell, rootDir: cwd });\n }\n\n const health = calculateHealth({\n layering,\n coupling,\n types,\n knip,\n totalPackages: coupling.packages.length,\n avgTestCoverage: null,\n thresholds: cfg.thresholds,\n });\n\n await ctx.platform.cache.set(CACHE_KEYS.HEALTH, health, CACHE_TTLS.FAST);\n\n outputHealth(health, flags, ctx);\n return { exitCode: health.score < cfg.thresholds.health ? 1 : 0 };\n },\n },\n});\n\nfunction outputHealth(health: Awaited<ReturnType<typeof calculateHealth>>, flags: { json?: boolean; detailed?: boolean }, ctx: PluginContextV3) {\n if (flags.json) {\n ctx.ui?.json?.(health);\n return;\n }\n\n const dimItems = Object.entries(health.dimensions).map(([key, dim]) => {\n const details = dim.details.length > 0 ? `: ${dim.details.join(', ')}` : '';\n return `${key} ${dim.score}/100 (${dim.grade})${details}`;\n });\n\n ctx.ui?.success?.(`Health score: ${health.score}/100 (${health.grade})`, {\n sections: [{ header: 'Dimensions', items: dimItems }],\n });\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
|
|
2
|
+
import { CLIInput } from '@kb-labs/sdk';
|
|
3
|
+
import { HistoryFlags } from './flags.js';
|
|
4
|
+
|
|
5
|
+
declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, CLIInput<HistoryFlags>, {
|
|
6
|
+
exitCode: number;
|
|
7
|
+
}>;
|
|
8
|
+
|
|
9
|
+
export { _default as default };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { defineCommand, useConfig } from '@kb-labs/sdk';
|
|
2
|
+
import { QualitySnapshotStore } from '@kb-labs/quality-core';
|
|
3
|
+
import { defaultQualityConfig } from '@kb-labs/quality-contracts';
|
|
4
|
+
|
|
5
|
+
// src/cli/commands/history.ts
|
|
6
|
+
var history_default = defineCommand({
|
|
7
|
+
id: "quality:history",
|
|
8
|
+
description: "Show quality snapshot history and trends",
|
|
9
|
+
handler: {
|
|
10
|
+
async execute(ctx, input) {
|
|
11
|
+
const { flags } = input;
|
|
12
|
+
const config = await useConfig();
|
|
13
|
+
const cwd = ctx.cwd ?? process.cwd();
|
|
14
|
+
const store = new QualitySnapshotStore(cwd, config?.maxSnapshots ?? defaultQualityConfig.maxSnapshots);
|
|
15
|
+
const { snapshots, delta, latest } = store.history();
|
|
16
|
+
if (snapshots.length === 0) {
|
|
17
|
+
ctx.ui?.success?.("No snapshots yet. Run `kb quality snapshot` first.");
|
|
18
|
+
return { exitCode: 0 };
|
|
19
|
+
}
|
|
20
|
+
const limit = flags.limit ?? 10;
|
|
21
|
+
const recent = snapshots.slice(-limit).reverse();
|
|
22
|
+
if (flags.json) {
|
|
23
|
+
ctx.ui?.json?.({ snapshots: recent, delta, latest });
|
|
24
|
+
return { exitCode: 0 };
|
|
25
|
+
}
|
|
26
|
+
const items = recent.map((s) => {
|
|
27
|
+
const date = new Date(s.timestamp).toLocaleString();
|
|
28
|
+
const branch = s.git.branch ? ` [${s.git.branch}]` : "";
|
|
29
|
+
return `${date}${branch} \u2014 ${s.score}/100 (${s.grade}) violations: ${s.counters.layeringViolations} any: ${s.counters.anyCount}`;
|
|
30
|
+
});
|
|
31
|
+
const sections = [
|
|
32
|
+
{ header: `Last ${recent.length} snapshots`, items }
|
|
33
|
+
];
|
|
34
|
+
if (delta) {
|
|
35
|
+
const sign = (n) => n > 0 ? `+${n}` : `${n}`;
|
|
36
|
+
sections.push({
|
|
37
|
+
header: "Delta vs previous snapshot",
|
|
38
|
+
items: [
|
|
39
|
+
`Score: ${sign(delta.score)}`,
|
|
40
|
+
`Layering violations: ${sign(delta.layeringViolations)}`,
|
|
41
|
+
`Any count: ${sign(delta.anyCount)}`,
|
|
42
|
+
`Unused files: ${sign(delta.unusedFiles)}`,
|
|
43
|
+
`Avg instability: ${sign(delta.avgInstability)}`
|
|
44
|
+
]
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
ctx.ui?.success?.(`Quality history (${recent.length} snapshots)`, { sections });
|
|
48
|
+
return { exitCode: 0 };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export { history_default as default };
|
|
54
|
+
//# sourceMappingURL=history.js.map
|
|
55
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/cli/commands/history.ts"],"names":[],"mappings":"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","file":"history.js","sourcesContent":["import {\n defineCommand,\n useConfig,\n type CLIInput,\n type PluginContextV3,\n} from '@kb-labs/sdk';\nimport { QualitySnapshotStore } from '@kb-labs/quality-core';\nimport { defaultQualityConfig, type QualityPluginConfig } from '@kb-labs/quality-contracts';\nimport type { HistoryFlags } from './flags.js';\n\nexport default defineCommand<unknown, CLIInput<HistoryFlags>, { exitCode: number }>({\n id: 'quality:history',\n description: 'Show quality snapshot history and trends',\n\n handler: {\n async execute(ctx: PluginContextV3, input: CLIInput<HistoryFlags>): Promise<{ exitCode: number }> {\n const { flags } = input;\n const config = await useConfig<QualityPluginConfig>();\n const cwd = ctx.cwd ?? process.cwd();\n\n const store = new QualitySnapshotStore(cwd, config?.maxSnapshots ?? defaultQualityConfig.maxSnapshots);\n const { snapshots, delta, latest } = store.history();\n\n if (snapshots.length === 0) {\n ctx.ui?.success?.('No snapshots yet. Run `kb quality snapshot` first.');\n return { exitCode: 0 };\n }\n\n const limit = flags.limit ?? 10;\n const recent = snapshots.slice(-limit).reverse();\n\n if (flags.json) {\n ctx.ui?.json?.({ snapshots: recent, delta, latest });\n return { exitCode: 0 };\n }\n\n const items = recent.map(s => {\n const date = new Date(s.timestamp).toLocaleString();\n const branch = s.git.branch ? ` [${s.git.branch}]` : '';\n return `${date}${branch} — ${s.score}/100 (${s.grade}) violations: ${s.counters.layeringViolations} any: ${s.counters.anyCount}`;\n });\n\n const sections: Array<{ header: string; items: string[] }> = [\n { header: `Last ${recent.length} snapshots`, items },\n ];\n\n if (delta) {\n const sign = (n: number) => (n > 0 ? `+${n}` : `${n}`);\n sections.push({\n header: 'Delta vs previous snapshot',\n items: [\n `Score: ${sign(delta.score)}`,\n `Layering violations: ${sign(delta.layeringViolations)}`,\n `Any count: ${sign(delta.anyCount)}`,\n `Unused files: ${sign(delta.unusedFiles)}`,\n `Avg instability: ${sign(delta.avgInstability)}`,\n ],\n });\n }\n\n ctx.ui?.success?.(`Quality history (${recent.length} snapshots)`, { sections });\n return { exitCode: 0 };\n },\n },\n});\n"]}
|
|
@@ -2,13 +2,17 @@ export { default as buildOrder } from './build-order.js';
|
|
|
2
2
|
export { default as checkBuilds } from './check-builds.js';
|
|
3
3
|
export { default as checkTests } from './check-tests.js';
|
|
4
4
|
export { default as checkTypes } from './check-types.js';
|
|
5
|
+
export { default as checkLayers } from './check-layers.js';
|
|
6
|
+
export { default as coupling } from './coupling.js';
|
|
5
7
|
export { default as cycles } from './cycles.js';
|
|
6
8
|
export { default as deadCode } from './dead-code.js';
|
|
7
9
|
export { default as fixDeps } from './fix-deps.js';
|
|
8
10
|
export { default as health } from './health.js';
|
|
11
|
+
export { default as history } from './history.js';
|
|
12
|
+
export { default as snapshot } from './snapshot.js';
|
|
9
13
|
export { default as stats } from './stats.js';
|
|
10
14
|
export { default as visualize } from './visualize.js';
|
|
11
15
|
import '@kb-labs/shared-command-kit';
|
|
12
|
-
import '@kb-labs/
|
|
13
|
-
import '@kb-labs/quality-contracts';
|
|
16
|
+
import '@kb-labs/sdk';
|
|
14
17
|
import './flags.js';
|
|
18
|
+
import '@kb-labs/quality-contracts';
|