@kb-labs/quality-core 0.6.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 ADDED
@@ -0,0 +1,63 @@
1
+ # @kb-labs/quality-core
2
+
3
+ Core business logic for KB Labs Quality Plugin. This package contains all the analysis and computation logic for monorepo quality analysis, separated from CLI and REST handlers.
4
+
5
+ ## Architecture
6
+
7
+ This package follows the **commit-plugin** pattern where business logic is extracted into a separate `core` package:
8
+
9
+ ```
10
+ quality-core/ # Business logic (this package)
11
+ quality-cli/ # CLI commands + REST handlers
12
+ quality-contracts/ # Shared types and schemas
13
+ ```
14
+
15
+ ## Modules
16
+
17
+ ### `stats` - Monorepo Statistics
18
+ - `calculateStats(rootDir: string): Promise<StatsResult>`
19
+ - Package counting, LOC analysis, size calculation
20
+
21
+ ### `health` - Health Scoring
22
+ - `calculateHealth(rootDir: string): Promise<HealthResult>`
23
+ - Dependency health, structure validation, grade A-F
24
+
25
+ ### `dependencies` - Dependency Analysis
26
+ - `analyzeDependencies(rootDir: string): Promise<DependencyAnalysis>`
27
+ - Find duplicates, unused, missing workspace dependencies
28
+
29
+ ### `build-order` - Topological Sort
30
+ - `calculateBuildOrder(rootDir: string): Promise<BuildOrderResult>`
31
+ - Topological sort with parallel build layers
32
+ - Circular dependency detection
33
+
34
+ ### `graph` - Dependency Graph
35
+ - `buildDependencyGraph(rootDir: string): DependencyGraph`
36
+ - Tree view, reverse dependencies, impact analysis
37
+
38
+ ## Usage
39
+
40
+ ```typescript
41
+ import { calculateStats } from '@kb-labs/quality-core/stats';
42
+ import { calculateHealth } from '@kb-labs/quality-core/health';
43
+ import { analyzeDependencies } from '@kb-labs/quality-core/dependencies';
44
+
45
+ // In CLI command
46
+ const stats = await calculateStats(ctx.cwd);
47
+ ctx.ui.success('Stats calculated', { data: stats });
48
+
49
+ // In REST handler
50
+ const health = await calculateHealth(ctx.cwd);
51
+ return { score: health.score, grade: health.grade };
52
+ ```
53
+
54
+ ## Why Separate Core?
55
+
56
+ - **Reusability**: CLI, REST handlers, and Studio all use same logic
57
+ - **Testability**: Easy to unit test without CLI/REST overhead
58
+ - **Maintainability**: Business logic changes don't affect handlers
59
+ - **Type Safety**: Single source of truth for calculations
60
+
61
+ ## License
62
+
63
+ MIT
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Check build status across monorepo packages
3
+ *
4
+ * Pure computation - no CLI, no dependencies on devkit
5
+ */
6
+ interface BuildCheckResult {
7
+ totalPackages: number;
8
+ passing: number;
9
+ failing: number;
10
+ failures: Array<{
11
+ package: string;
12
+ error: string;
13
+ exitCode: number;
14
+ }>;
15
+ staleBuilds: Array<{
16
+ package: string;
17
+ distMtime: number;
18
+ srcMtime: number;
19
+ }>;
20
+ duration: number;
21
+ }
22
+ interface BuildCheckOptions {
23
+ packageFilter?: string;
24
+ includeDevDeps?: boolean;
25
+ timeout?: number;
26
+ }
27
+ /**
28
+ * Check builds across monorepo
29
+ *
30
+ * @returns Build check results with failures and stale builds
31
+ */
32
+ declare function checkBuilds(rootDir: string, options?: BuildCheckOptions): Promise<BuildCheckResult>;
33
+
34
+ export { type BuildCheckOptions, type BuildCheckResult, checkBuilds };
@@ -0,0 +1,133 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+
6
+ // src/builds/check-builds.ts
7
+ var execAsync = promisify(exec);
8
+ function findPackagesWithBuildScript(rootDir, filter) {
9
+ const packages = [];
10
+ if (!fs.existsSync(rootDir)) {
11
+ return packages;
12
+ }
13
+ const entries = fs.readdirSync(rootDir, { withFileTypes: true });
14
+ for (const entry of entries) {
15
+ if (!entry.isDirectory() || !entry.name.startsWith("kb-labs-")) {
16
+ continue;
17
+ }
18
+ const repoPath = path.join(rootDir, entry.name);
19
+ const packagesDir = path.join(repoPath, "packages");
20
+ if (!fs.existsSync(packagesDir)) {
21
+ continue;
22
+ }
23
+ const packageDirs = fs.readdirSync(packagesDir, { withFileTypes: true });
24
+ for (const pkgDir of packageDirs) {
25
+ if (!pkgDir.isDirectory()) {
26
+ continue;
27
+ }
28
+ const packageJsonPath = path.join(packagesDir, pkgDir.name, "package.json");
29
+ if (fs.existsSync(packageJsonPath)) {
30
+ const pkgJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
31
+ if (!pkgJson.scripts?.build) {
32
+ continue;
33
+ }
34
+ if (filter && !pkgJson.name.includes(filter) && !pkgDir.name.includes(filter)) {
35
+ continue;
36
+ }
37
+ packages.push(packageJsonPath);
38
+ }
39
+ }
40
+ }
41
+ return packages;
42
+ }
43
+ function isDistStale(packageDir) {
44
+ const distFile = path.join(packageDir, "dist/index.js");
45
+ const srcDir = path.join(packageDir, "src");
46
+ if (!fs.existsSync(distFile)) {
47
+ return { stale: false };
48
+ }
49
+ if (!fs.existsSync(srcDir)) {
50
+ return { stale: false };
51
+ }
52
+ const distMtime = fs.statSync(distFile).mtime.getTime();
53
+ let newestSrcMtime = 0;
54
+ function walkDir(dir) {
55
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
56
+ for (const entry of entries) {
57
+ const fullPath = path.join(dir, entry.name);
58
+ if (entry.isDirectory()) {
59
+ walkDir(fullPath);
60
+ } else if (/\.(ts|tsx|js|jsx)$/.test(entry.name)) {
61
+ const mtime = fs.statSync(fullPath).mtime.getTime();
62
+ if (mtime > newestSrcMtime) {
63
+ newestSrcMtime = mtime;
64
+ }
65
+ }
66
+ }
67
+ }
68
+ walkDir(srcDir);
69
+ return {
70
+ stale: newestSrcMtime > distMtime,
71
+ distMtime,
72
+ srcMtime: newestSrcMtime
73
+ };
74
+ }
75
+ async function tryBuildPackage(packageDir, packageName, timeout) {
76
+ try {
77
+ await execAsync("pnpm run build", {
78
+ cwd: packageDir,
79
+ timeout,
80
+ encoding: "utf-8"
81
+ });
82
+ return { success: true };
83
+ } catch (err) {
84
+ return {
85
+ success: false,
86
+ error: err.stderr?.trim() || err.message,
87
+ exitCode: err.code || 1
88
+ };
89
+ }
90
+ }
91
+ async function checkBuilds(rootDir, options = {}) {
92
+ const startTime = Date.now();
93
+ const timeout = options.timeout || 3e4;
94
+ const packagePaths = findPackagesWithBuildScript(rootDir, options.packageFilter);
95
+ const result = {
96
+ totalPackages: packagePaths.length,
97
+ passing: 0,
98
+ failing: 0,
99
+ failures: [],
100
+ staleBuilds: [],
101
+ duration: 0
102
+ };
103
+ for (const pkgPath of packagePaths) {
104
+ const pkgJson = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
105
+ const packageName = pkgJson.name;
106
+ const packageDir = path.dirname(pkgPath);
107
+ const staleCheck = isDistStale(packageDir);
108
+ if (staleCheck.stale) {
109
+ result.staleBuilds.push({
110
+ package: packageName,
111
+ distMtime: staleCheck.distMtime,
112
+ srcMtime: staleCheck.srcMtime
113
+ });
114
+ }
115
+ const buildResult = await tryBuildPackage(packageDir, packageName, timeout);
116
+ if (buildResult.success) {
117
+ result.passing++;
118
+ } else {
119
+ result.failing++;
120
+ result.failures.push({
121
+ package: packageName,
122
+ error: buildResult.error,
123
+ exitCode: buildResult.exitCode
124
+ });
125
+ }
126
+ }
127
+ result.duration = Date.now() - startTime;
128
+ return result;
129
+ }
130
+
131
+ export { checkBuilds };
132
+ //# sourceMappingURL=index.js.map
133
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/builds/check-builds.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Check build status across monorepo packages\n *\n * Pure computation - no CLI, no dependencies on devkit\n */\n\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nconst execAsync = promisify(exec);\n\nexport interface BuildCheckResult {\n totalPackages: number;\n passing: number;\n failing: number;\n failures: Array<{\n package: string;\n error: string;\n exitCode: number;\n }>;\n staleBuilds: Array<{\n package: string;\n distMtime: number;\n srcMtime: number;\n }>;\n duration: number;\n}\n\nexport interface BuildCheckOptions {\n packageFilter?: string;\n includeDevDeps?: boolean;\n timeout?: number; // per-package timeout in ms\n}\n\n/**\n * Find all packages with build scripts\n */\nfunction findPackagesWithBuildScript(rootDir: string, filter?: 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 const pkgJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));\n\n // Skip if no build script\n if (!pkgJson.scripts?.build) {continue;}\n\n // Skip if filter doesn't match\n if (filter && !pkgJson.name.includes(filter) && !pkgDir.name.includes(filter)) {\n continue;\n }\n\n packages.push(packageJsonPath);\n }\n }\n }\n\n return packages;\n}\n\n/**\n * Check if dist/ is stale (older than src/)\n */\nfunction isDistStale(packageDir: string): {\n stale: boolean;\n distMtime?: number;\n srcMtime?: number;\n} {\n const distFile = path.join(packageDir, 'dist/index.js');\n const srcDir = path.join(packageDir, 'src');\n\n if (!fs.existsSync(distFile)) {\n return { stale: false };\n }\n\n if (!fs.existsSync(srcDir)) {\n return { stale: false };\n }\n\n const distMtime = fs.statSync(distFile).mtime.getTime();\n\n // Find newest file in src/\n let newestSrcMtime = 0;\n\n function walkDir(dir: string) {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walkDir(fullPath);\n } else if (/\\.(ts|tsx|js|jsx)$/.test(entry.name)) {\n const mtime = fs.statSync(fullPath).mtime.getTime();\n if (mtime > newestSrcMtime) {\n newestSrcMtime = mtime;\n }\n }\n }\n }\n\n walkDir(srcDir);\n\n return {\n stale: newestSrcMtime > distMtime,\n distMtime,\n srcMtime: newestSrcMtime,\n };\n}\n\n/**\n * Try to build a single package\n */\nasync function tryBuildPackage(\n packageDir: string,\n packageName: string,\n timeout: number\n): Promise<{ success: boolean; error?: string; exitCode?: number }> {\n try {\n await execAsync('pnpm run build', {\n cwd: packageDir,\n timeout,\n encoding: 'utf-8',\n });\n\n return { success: true };\n } catch (err: any) {\n return {\n success: false,\n error: err.stderr?.trim() || err.message,\n exitCode: err.code || 1,\n };\n }\n}\n\n/**\n * Check builds across monorepo\n *\n * @returns Build check results with failures and stale builds\n */\nexport async function checkBuilds(\n rootDir: string,\n options: BuildCheckOptions = {}\n): Promise<BuildCheckResult> {\n const startTime = Date.now();\n const timeout = options.timeout || 30000; // 30s default\n\n const packagePaths = findPackagesWithBuildScript(rootDir, options.packageFilter);\n\n const result: BuildCheckResult = {\n totalPackages: packagePaths.length,\n passing: 0,\n failing: 0,\n failures: [],\n staleBuilds: [],\n duration: 0,\n };\n\n // Check builds sequentially (parallel would be too heavy)\n for (const pkgPath of packagePaths) {\n const pkgJson = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));\n const packageName = pkgJson.name;\n const packageDir = path.dirname(pkgPath);\n\n // Check if dist is stale\n const staleCheck = isDistStale(packageDir);\n if (staleCheck.stale) {\n result.staleBuilds.push({\n package: packageName,\n distMtime: staleCheck.distMtime!,\n srcMtime: staleCheck.srcMtime!,\n });\n }\n\n // Try to build\n const buildResult = await tryBuildPackage(packageDir, packageName, timeout);\n\n if (buildResult.success) {\n result.passing++;\n } else {\n result.failing++;\n result.failures.push({\n package: packageName,\n error: buildResult.error!,\n exitCode: buildResult.exitCode!,\n });\n }\n }\n\n result.duration = Date.now() - startTime;\n\n return result;\n}\n"]}
@@ -0,0 +1,121 @@
1
+ import { DeadCodeOptions, DeadCodeResult, DeadCodeRemovalResult, DeadCodeBackupManifest } from '@kb-labs/quality-contracts';
2
+
3
+ /**
4
+ * Dead code file scanner — main orchestrator
5
+ *
6
+ * Per-package analysis: collect entry points, build import graph,
7
+ * BFS reachability, report unreachable files.
8
+ */
9
+
10
+ /**
11
+ * Scan the monorepo for dead (unreachable) source files.
12
+ *
13
+ * For each package:
14
+ * 1. Collect entry points (package.json, tsup, manifest, tests, configs)
15
+ * 2. Build file-level import graph
16
+ * 3. BFS from entry points to find reachable files
17
+ * 4. Everything not reachable = dead
18
+ */
19
+ declare function scanDeadFiles(rootDir: string, options?: DeadCodeOptions): Promise<DeadCodeResult>;
20
+
21
+ /**
22
+ * Entry point collection for dead code detection
23
+ *
24
+ * Collects all files that are "alive by definition" — entry points from
25
+ * package.json, tsup.config.ts, manifest.ts, tests, and configs.
26
+ *
27
+ * Fail-open: if we can't parse a config, we emit a warning and treat
28
+ * ALL files in the package as alive (zero false positives).
29
+ */
30
+ interface EntryPointResult {
31
+ /** Absolute paths of entry point files (roots for BFS) */
32
+ entryFiles: Set<string>;
33
+ /** Files alive by convention (tests, configs) — not part of graph traversal */
34
+ aliveByConvention: Set<string>;
35
+ /** Non-fatal issues during parsing */
36
+ warnings: string[];
37
+ /** If true, couldn't parse configs — all files should be treated as alive */
38
+ failOpen: boolean;
39
+ }
40
+ /**
41
+ * Collect all entry points for a package.
42
+ *
43
+ * A file is an entry point if it's referenced by package.json, tsup.config.ts,
44
+ * or manifest.ts. Tests and config files are always alive by convention.
45
+ */
46
+ declare function collectEntryPoints(packageDir: string, packageJson: Record<string, unknown>): Promise<EntryPointResult>;
47
+ /**
48
+ * Map a dist/ path to its src/ counterpart.
49
+ * dist/index.js → src/index.ts
50
+ * dist/sandbox/bootstrap.js → src/sandbox/bootstrap.ts
51
+ */
52
+ declare function distPathToSrcPath(distPath: string, packageDir: string): string | null;
53
+ /**
54
+ * Parse entry points from tsup config file content.
55
+ * Handles: string, array of strings, object of strings.
56
+ */
57
+ declare function parseTsupEntries(content: string): string[];
58
+ /**
59
+ * Extract handler paths from manifest.ts content.
60
+ * Matches: handler: './path/to/file.js#export' and handlerPath: './path/to/file.js'
61
+ */
62
+ declare function parseManifestHandlers(content: string): string[];
63
+
64
+ /**
65
+ * File-level import graph and BFS reachability analysis
66
+ *
67
+ * Builds a directed graph of file → imported files, then walks from
68
+ * entry points to find all reachable (alive) files.
69
+ */
70
+ /**
71
+ * Extract all import paths from a source file's content.
72
+ * Returns raw import specifiers (both relative and package).
73
+ */
74
+ declare function extractFileImports(content: string): string[];
75
+ /**
76
+ * Resolve a relative import to an absolute file path.
77
+ *
78
+ * Handles ESM convention where source code imports './foo.js'
79
+ * but the actual file is './foo.ts'.
80
+ */
81
+ declare function resolveRelativeImport(specifier: string, sourceFile: string): string | null;
82
+ /**
83
+ * Build a file-level import graph for all source files.
84
+ *
85
+ * Returns a map: absolute file path → set of absolute imported file paths.
86
+ * Only includes relative imports (not external packages).
87
+ */
88
+ declare function buildFileImportGraph(sourceFiles: string[]): Map<string, Set<string>>;
89
+ /**
90
+ * BFS from entry points through the import graph.
91
+ * Returns the set of all reachable file paths.
92
+ */
93
+ declare function findReachableFiles(entryPoints: Set<string>, importGraph: Map<string, Set<string>>): Set<string>;
94
+
95
+ /**
96
+ * Backup, restore, and auto-removal for dead code files
97
+ *
98
+ * Creates timestamped backups before deletion, supports full restore,
99
+ * and cleans up empty directories + package.json exports.
100
+ */
101
+
102
+ /**
103
+ * Remove dead files with full backup.
104
+ * Creates a timestamped backup directory, copies files, then deletes.
105
+ */
106
+ declare function removeDeadFiles(rootDir: string, scanResult: DeadCodeResult, options?: {
107
+ dryRun?: boolean;
108
+ }): Promise<DeadCodeRemovalResult>;
109
+ /**
110
+ * Restore files from a backup.
111
+ */
112
+ declare function restoreFromBackup(rootDir: string, backupId: string): Promise<{
113
+ restoredFiles: number;
114
+ restoredExports: number;
115
+ }>;
116
+ /**
117
+ * List all available backups.
118
+ */
119
+ declare function listBackups(rootDir: string): DeadCodeBackupManifest[];
120
+
121
+ export { buildFileImportGraph, collectEntryPoints, distPathToSrcPath, extractFileImports, findReachableFiles, listBackups, parseManifestHandlers, parseTsupEntries, removeDeadFiles, resolveRelativeImport, restoreFromBackup, scanDeadFiles };