@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.
@@ -0,0 +1,200 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+
6
+ // src/tests/run-tests.ts
7
+ var execAsync = promisify(exec);
8
+ function findPackagesWithTests(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 (filter && !pkgJson.name.includes(filter) && !pkgDir.name.includes(filter)) {
32
+ continue;
33
+ }
34
+ packages.push({
35
+ name: pkgJson.name,
36
+ dir: path.dirname(packageJsonPath),
37
+ hasTestScript: !!pkgJson.scripts?.test
38
+ });
39
+ }
40
+ }
41
+ }
42
+ return packages;
43
+ }
44
+ async function runPackageTests(packageDir, packageName, timeout) {
45
+ try {
46
+ const { stdout, stderr } = await execAsync("pnpm test", {
47
+ cwd: packageDir,
48
+ timeout,
49
+ env: { ...process.env, CI: "true" }
50
+ // CI mode for non-interactive tests
51
+ });
52
+ return {
53
+ success: true,
54
+ exitCode: 0,
55
+ output: (stdout || "") + (stderr || "")
56
+ };
57
+ } catch (err) {
58
+ return {
59
+ success: false,
60
+ exitCode: err.code || 1,
61
+ error: err.message || "Test execution failed",
62
+ output: (err.stdout || "") + (err.stderr || "")
63
+ };
64
+ }
65
+ }
66
+ function parseTestOutput(output) {
67
+ const vitestMatch = output.match(/Test Files\s+(\d+)\s+passed/);
68
+ const jestMatch = output.match(/Tests:\s+(\d+)\s+passed,\s+(\d+)\s+total/);
69
+ const jestFailMatch = output.match(/(\d+)\s+failed,\s+(\d+)\s+passed,\s+(\d+)\s+total/);
70
+ if (jestFailMatch && jestFailMatch[1] && jestFailMatch[2] && jestFailMatch[3]) {
71
+ return {
72
+ failed: parseInt(jestFailMatch[1], 10),
73
+ passed: parseInt(jestFailMatch[2], 10),
74
+ total: parseInt(jestFailMatch[3], 10)
75
+ };
76
+ }
77
+ if (jestMatch && jestMatch[1] && jestMatch[2]) {
78
+ return {
79
+ passed: parseInt(jestMatch[1], 10),
80
+ total: parseInt(jestMatch[2], 10)
81
+ };
82
+ }
83
+ if (vitestMatch && vitestMatch[1]) {
84
+ return {
85
+ passed: parseInt(vitestMatch[1], 10),
86
+ total: parseInt(vitestMatch[1], 10)
87
+ };
88
+ }
89
+ return {};
90
+ }
91
+ function readCoverage(packageDir) {
92
+ const coveragePath = path.join(packageDir, "coverage", "coverage-summary.json");
93
+ if (!fs.existsSync(coveragePath)) {
94
+ return null;
95
+ }
96
+ try {
97
+ const coverageData = JSON.parse(fs.readFileSync(coveragePath, "utf-8"));
98
+ const total = coverageData.total;
99
+ return {
100
+ lines: total?.lines?.pct || 0,
101
+ statements: total?.statements?.pct || 0,
102
+ functions: total?.functions?.pct || 0,
103
+ branches: total?.branches?.pct || 0
104
+ };
105
+ } catch {
106
+ return null;
107
+ }
108
+ }
109
+ async function runTests(rootDir, options = {}) {
110
+ const startTime = Date.now();
111
+ const timeout = options.timeout || 6e4;
112
+ const packages = findPackagesWithTests(rootDir, options.packageFilter);
113
+ const result = {
114
+ totalPackages: packages.length,
115
+ passing: 0,
116
+ failing: 0,
117
+ skipped: 0,
118
+ failures: [],
119
+ summary: {
120
+ totalTests: 0,
121
+ passedTests: 0,
122
+ failedTests: 0
123
+ },
124
+ coverage: {
125
+ avgCoverage: 0,
126
+ packages: []
127
+ },
128
+ duration: 0
129
+ };
130
+ for (const { name, dir, hasTestScript } of packages) {
131
+ if (!hasTestScript) {
132
+ result.skipped++;
133
+ continue;
134
+ }
135
+ if (options.coverageOnly) {
136
+ const coverage = readCoverage(dir);
137
+ if (coverage) {
138
+ result.coverage.packages.push({
139
+ name,
140
+ ...coverage
141
+ });
142
+ }
143
+ continue;
144
+ }
145
+ const testResult = await runPackageTests(dir, name, timeout);
146
+ if (testResult.success) {
147
+ result.passing++;
148
+ if (testResult.output) {
149
+ const counts = parseTestOutput(testResult.output);
150
+ if (counts.total) {
151
+ result.summary.totalTests += counts.total;
152
+ }
153
+ if (counts.passed) {
154
+ result.summary.passedTests += counts.passed;
155
+ }
156
+ if (counts.failed) {
157
+ result.summary.failedTests += counts.failed || 0;
158
+ }
159
+ }
160
+ } else {
161
+ result.failing++;
162
+ const counts = testResult.output ? parseTestOutput(testResult.output) : {};
163
+ result.failures.push({
164
+ package: name,
165
+ error: testResult.error || "Unknown error",
166
+ exitCode: testResult.exitCode,
167
+ failedTests: counts.failed,
168
+ totalTests: counts.total
169
+ });
170
+ if (counts.total) {
171
+ result.summary.totalTests += counts.total;
172
+ }
173
+ if (counts.passed) {
174
+ result.summary.passedTests += counts.passed || 0;
175
+ }
176
+ if (counts.failed) {
177
+ result.summary.failedTests += counts.failed;
178
+ }
179
+ }
180
+ if (options.withCoverage) {
181
+ const coverage = readCoverage(dir);
182
+ if (coverage) {
183
+ result.coverage.packages.push({
184
+ name,
185
+ ...coverage
186
+ });
187
+ }
188
+ }
189
+ }
190
+ if (result.coverage.packages.length > 0) {
191
+ const totalLines = result.coverage.packages.reduce((sum, pkg) => sum + pkg.lines, 0);
192
+ result.coverage.avgCoverage = totalLines / result.coverage.packages.length;
193
+ }
194
+ result.duration = Date.now() - startTime;
195
+ return result;
196
+ }
197
+
198
+ export { runTests };
199
+ //# sourceMappingURL=index.js.map
200
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/tests/run-tests.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Test execution and coverage tracking\n *\n * Runs tests across monorepo packages and collects coverage statistics\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 TestRunOptions {\n packageFilter?: string;\n timeout?: number;\n withCoverage?: boolean;\n coverageOnly?: boolean;\n concurrency?: number; // Max parallel test runs (default: 3)\n}\n\nexport interface TestRunResult {\n totalPackages: number;\n passing: number;\n failing: number;\n skipped: number;\n failures: Array<{\n package: string;\n error: string;\n exitCode: number;\n failedTests?: number;\n totalTests?: number;\n }>;\n summary: {\n totalTests: number;\n passedTests: number;\n failedTests: number;\n };\n coverage: {\n avgCoverage: number;\n packages: Array<{\n name: string;\n lines: number;\n statements: number;\n functions: number;\n branches: number;\n }>;\n };\n duration: number;\n}\n\ninterface PackageWithTests {\n name: string;\n dir: string;\n hasTestScript: boolean;\n}\n\n/**\n * Find all packages with test scripts\n */\nfunction findPackagesWithTests(rootDir: string, filter?: string): PackageWithTests[] {\n const packages: PackageWithTests[] = [];\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 filter doesn't match\n if (filter && !pkgJson.name.includes(filter) && !pkgDir.name.includes(filter)) {\n continue;\n }\n\n packages.push({\n name: pkgJson.name,\n dir: path.dirname(packageJsonPath),\n hasTestScript: !!pkgJson.scripts?.test,\n });\n }\n }\n }\n\n return packages;\n}\n\n/**\n * Run tests for a package\n */\nasync function runPackageTests(\n packageDir: string,\n packageName: string,\n timeout: number\n): Promise<{ success: boolean; exitCode: number; error?: string; output?: string }> {\n try {\n const { stdout, stderr } = await execAsync('pnpm test', {\n cwd: packageDir,\n timeout,\n env: { ...process.env, CI: 'true' }, // CI mode for non-interactive tests\n });\n\n return {\n success: true,\n exitCode: 0,\n output: (stdout || '') + (stderr || ''),\n };\n } catch (err: any) {\n return {\n success: false,\n exitCode: err.code || 1,\n error: err.message || 'Test execution failed',\n output: (err.stdout || '') + (err.stderr || ''),\n };\n }\n}\n\n/**\n * Parse test output to extract test counts (vitest/jest)\n */\nfunction parseTestOutput(output: string): { total?: number; passed?: number; failed?: number } {\n // Vitest format: \"Test Files 1 passed (1)\"\n // Jest format: \"Tests: 5 passed, 5 total\"\n\n const vitestMatch = output.match(/Test Files\\s+(\\d+)\\s+passed/);\n const jestMatch = output.match(/Tests:\\s+(\\d+)\\s+passed,\\s+(\\d+)\\s+total/);\n const jestFailMatch = output.match(/(\\d+)\\s+failed,\\s+(\\d+)\\s+passed,\\s+(\\d+)\\s+total/);\n\n if (jestFailMatch && jestFailMatch[1] && jestFailMatch[2] && jestFailMatch[3]) {\n return {\n failed: parseInt(jestFailMatch[1], 10),\n passed: parseInt(jestFailMatch[2], 10),\n total: parseInt(jestFailMatch[3], 10),\n };\n }\n\n if (jestMatch && jestMatch[1] && jestMatch[2]) {\n return {\n passed: parseInt(jestMatch[1], 10),\n total: parseInt(jestMatch[2], 10),\n };\n }\n\n if (vitestMatch && vitestMatch[1]) {\n return {\n passed: parseInt(vitestMatch[1], 10),\n total: parseInt(vitestMatch[1], 10),\n };\n }\n\n return {};\n}\n\n/**\n * Run async tasks with concurrency limit\n */\nasync function runWithConcurrency<T, R>(\n items: T[],\n handler: (item: T) => Promise<R>,\n concurrency: number\n): Promise<R[]> {\n const results: R[] = [];\n const executing: Promise<void>[] = [];\n\n for (const item of items) {\n const promise = handler(item).then(result => {\n results.push(result);\n });\n\n executing.push(promise);\n\n if (executing.length >= concurrency) {\n await Promise.race(executing);\n executing.splice(\n executing.findIndex(p => p === promise),\n 1\n );\n }\n }\n\n await Promise.all(executing);\n return results;\n}\n\n/**\n * Read coverage data from coverage-summary.json\n */\nfunction readCoverage(packageDir: string): {\n lines: number;\n statements: number;\n functions: number;\n branches: number;\n} | null {\n const coveragePath = path.join(packageDir, 'coverage', 'coverage-summary.json');\n\n if (!fs.existsSync(coveragePath)) {\n return null;\n }\n\n try {\n const coverageData = JSON.parse(fs.readFileSync(coveragePath, 'utf-8'));\n const total = coverageData.total;\n\n return {\n lines: total?.lines?.pct || 0,\n statements: total?.statements?.pct || 0,\n functions: total?.functions?.pct || 0,\n branches: total?.branches?.pct || 0,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Run tests across monorepo\n */\nexport async function runTests(\n rootDir: string,\n options: TestRunOptions = {}\n): Promise<TestRunResult> {\n const startTime = Date.now();\n const timeout = options.timeout || 60000; // 60s per package\n const packages = findPackagesWithTests(rootDir, options.packageFilter);\n\n const result: TestRunResult = {\n totalPackages: packages.length,\n passing: 0,\n failing: 0,\n skipped: 0,\n failures: [],\n summary: {\n totalTests: 0,\n passedTests: 0,\n failedTests: 0,\n },\n coverage: {\n avgCoverage: 0,\n packages: [],\n },\n duration: 0,\n };\n\n for (const { name, dir, hasTestScript } of packages) {\n if (!hasTestScript) {\n result.skipped++;\n continue;\n }\n\n // Coverage-only mode: just read existing coverage\n if (options.coverageOnly) {\n const coverage = readCoverage(dir);\n if (coverage) {\n result.coverage.packages.push({\n name,\n ...coverage,\n });\n }\n continue;\n }\n\n // Run tests\n const testResult = await runPackageTests(dir, name, timeout);\n\n if (testResult.success) {\n result.passing++;\n\n // Parse test counts\n if (testResult.output) {\n const counts = parseTestOutput(testResult.output);\n if (counts.total) {result.summary.totalTests += counts.total;}\n if (counts.passed) {result.summary.passedTests += counts.passed;}\n if (counts.failed) {result.summary.failedTests += counts.failed || 0;}\n }\n } else {\n result.failing++;\n\n // Parse failure info\n const counts = testResult.output ? parseTestOutput(testResult.output) : {};\n\n result.failures.push({\n package: name,\n error: testResult.error || 'Unknown error',\n exitCode: testResult.exitCode,\n failedTests: counts.failed,\n totalTests: counts.total,\n });\n\n if (counts.total) {result.summary.totalTests += counts.total;}\n if (counts.passed) {result.summary.passedTests += counts.passed || 0;}\n if (counts.failed) {result.summary.failedTests += counts.failed;}\n }\n\n // Collect coverage if requested\n if (options.withCoverage) {\n const coverage = readCoverage(dir);\n if (coverage) {\n result.coverage.packages.push({\n name,\n ...coverage,\n });\n }\n }\n }\n\n // Calculate average coverage\n if (result.coverage.packages.length > 0) {\n const totalLines = result.coverage.packages.reduce((sum, pkg) => sum + pkg.lines, 0);\n result.coverage.avgCoverage = totalLines / result.coverage.packages.length;\n }\n\n result.duration = Date.now() - startTime;\n\n return result;\n}\n"]}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * TypeScript type safety analysis
3
+ *
4
+ * Uses TypeScript Compiler API for semantic analysis
5
+ * Simplified version compared to devkit-types-audit (focuses on essential metrics)
6
+ */
7
+ interface TypeAnalysisResult {
8
+ totalPackages: number;
9
+ packagesWithErrors: number;
10
+ totalErrors: number;
11
+ totalWarnings: number;
12
+ avgCoverage: number;
13
+ packages: Array<{
14
+ name: string;
15
+ errors: number;
16
+ warnings: number;
17
+ coverage: number;
18
+ anyCount: number;
19
+ tsIgnoreCount: number;
20
+ }>;
21
+ duration: number;
22
+ }
23
+ interface TypeAnalysisOptions {
24
+ packageFilter?: string;
25
+ errorsOnly?: boolean;
26
+ }
27
+ /**
28
+ * Analyze types across monorepo
29
+ */
30
+ declare function analyzeTypes(rootDir: string, options?: TypeAnalysisOptions): Promise<TypeAnalysisResult>;
31
+
32
+ export { type TypeAnalysisOptions, type TypeAnalysisResult, analyzeTypes };
@@ -0,0 +1,159 @@
1
+ import ts from 'typescript';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ // src/types/analyze-types.ts
6
+ function findPackagesWithTsConfig(rootDir, filter) {
7
+ const packages = [];
8
+ if (!fs.existsSync(rootDir)) {
9
+ return packages;
10
+ }
11
+ const entries = fs.readdirSync(rootDir, { withFileTypes: true });
12
+ for (const entry of entries) {
13
+ if (!entry.isDirectory() || !entry.name.startsWith("kb-labs-")) {
14
+ continue;
15
+ }
16
+ const repoPath = path.join(rootDir, entry.name);
17
+ const packagesDir = path.join(repoPath, "packages");
18
+ if (!fs.existsSync(packagesDir)) {
19
+ continue;
20
+ }
21
+ const packageDirs = fs.readdirSync(packagesDir, { withFileTypes: true });
22
+ for (const pkgDir of packageDirs) {
23
+ if (!pkgDir.isDirectory()) {
24
+ continue;
25
+ }
26
+ const packageJsonPath = path.join(packagesDir, pkgDir.name, "package.json");
27
+ const tsconfigPath = path.join(packagesDir, pkgDir.name, "tsconfig.json");
28
+ if (fs.existsSync(packageJsonPath) && fs.existsSync(tsconfigPath)) {
29
+ const pkgJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
30
+ if (filter && !pkgJson.name.includes(filter) && !pkgDir.name.includes(filter)) {
31
+ continue;
32
+ }
33
+ packages.push({
34
+ name: pkgJson.name,
35
+ dir: path.dirname(packageJsonPath),
36
+ tsconfigPath
37
+ });
38
+ }
39
+ }
40
+ }
41
+ return packages;
42
+ }
43
+ function createProgram(packageDir, tsconfigPath) {
44
+ try {
45
+ const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
46
+ if (configFile.error) {
47
+ return null;
48
+ }
49
+ const parsedConfig = ts.parseJsonConfigFileContent(
50
+ configFile.config,
51
+ ts.sys,
52
+ packageDir
53
+ );
54
+ if (parsedConfig.errors.length > 0) {
55
+ return null;
56
+ }
57
+ return ts.createProgram({
58
+ rootNames: parsedConfig.fileNames,
59
+ options: parsedConfig.options
60
+ });
61
+ } catch (err) {
62
+ return null;
63
+ }
64
+ }
65
+ function analyzePackageTypes(program) {
66
+ const diagnostics = ts.getPreEmitDiagnostics(program);
67
+ let errors = 0;
68
+ let warnings = 0;
69
+ for (const diagnostic of diagnostics) {
70
+ if (!diagnostic.file) {
71
+ continue;
72
+ }
73
+ if (diagnostic.category === ts.DiagnosticCategory.Error) {
74
+ errors++;
75
+ } else {
76
+ warnings++;
77
+ }
78
+ }
79
+ return { errors, warnings };
80
+ }
81
+ function calculateTypeCoverage(program) {
82
+ const checker = program.getTypeChecker();
83
+ const sourceFiles = program.getSourceFiles().filter(
84
+ (sf) => !sf.isDeclarationFile && !sf.fileName.includes("node_modules")
85
+ );
86
+ let totalSymbols = 0;
87
+ let typedSymbols = 0;
88
+ let anyCount = 0;
89
+ let tsIgnoreCount = 0;
90
+ for (const sourceFile of sourceFiles) {
91
+ const text = sourceFile.getFullText();
92
+ const tsIgnoreMatches = text.match(/@ts-ignore/g);
93
+ tsIgnoreCount += tsIgnoreMatches ? tsIgnoreMatches.length : 0;
94
+ ts.forEachChild(sourceFile, function visit(node) {
95
+ if (ts.isTypeNode(node)) {
96
+ totalSymbols++;
97
+ const type = checker.getTypeAtLocation(node);
98
+ if (type.flags & ts.TypeFlags.Any) {
99
+ anyCount++;
100
+ } else {
101
+ typedSymbols++;
102
+ }
103
+ }
104
+ ts.forEachChild(node, visit);
105
+ });
106
+ }
107
+ const coverage = totalSymbols > 0 ? typedSymbols / totalSymbols * 100 : 100;
108
+ return {
109
+ coverage: Math.round(coverage * 10) / 10,
110
+ totalSymbols,
111
+ typedSymbols,
112
+ anyCount,
113
+ tsIgnoreCount
114
+ };
115
+ }
116
+ async function analyzeTypes(rootDir, options = {}) {
117
+ const startTime = Date.now();
118
+ const packages = findPackagesWithTsConfig(rootDir, options.packageFilter);
119
+ const result = {
120
+ totalPackages: packages.length,
121
+ packagesWithErrors: 0,
122
+ totalErrors: 0,
123
+ totalWarnings: 0,
124
+ avgCoverage: 0,
125
+ packages: [],
126
+ duration: 0
127
+ };
128
+ for (const { name, dir, tsconfigPath } of packages) {
129
+ const program = createProgram(dir, tsconfigPath);
130
+ if (!program) {
131
+ continue;
132
+ }
133
+ const { errors, warnings } = analyzePackageTypes(program);
134
+ const coverage = calculateTypeCoverage(program);
135
+ result.totalErrors += errors;
136
+ result.totalWarnings += warnings;
137
+ if (errors > 0) {
138
+ result.packagesWithErrors++;
139
+ }
140
+ result.packages.push({
141
+ name,
142
+ errors,
143
+ warnings,
144
+ coverage: coverage.coverage,
145
+ anyCount: coverage.anyCount,
146
+ tsIgnoreCount: coverage.tsIgnoreCount
147
+ });
148
+ }
149
+ if (result.packages.length > 0) {
150
+ result.avgCoverage = result.packages.reduce((sum, p) => sum + p.coverage, 0) / result.packages.length;
151
+ result.avgCoverage = Math.round(result.avgCoverage * 10) / 10;
152
+ }
153
+ result.duration = Date.now() - startTime;
154
+ return result;
155
+ }
156
+
157
+ export { analyzeTypes };
158
+ //# sourceMappingURL=index.js.map
159
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/types/analyze-types.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * TypeScript type safety analysis\n *\n * Uses TypeScript Compiler API for semantic analysis\n * Simplified version compared to devkit-types-audit (focuses on essential metrics)\n */\n\nimport ts from 'typescript';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nexport interface TypeAnalysisResult {\n totalPackages: number;\n packagesWithErrors: number;\n totalErrors: number;\n totalWarnings: number;\n avgCoverage: number;\n packages: Array<{\n name: string;\n errors: number;\n warnings: number;\n coverage: number;\n anyCount: number;\n tsIgnoreCount: number;\n }>;\n duration: number;\n}\n\nexport interface TypeAnalysisOptions {\n packageFilter?: string;\n errorsOnly?: boolean;\n}\n\ninterface PackageWithTsConfig {\n name: string;\n dir: string;\n tsconfigPath: string;\n}\n\n/**\n * Find all packages with tsconfig.json\n */\nfunction findPackagesWithTsConfig(rootDir: string, filter?: string): PackageWithTsConfig[] {\n const packages: PackageWithTsConfig[] = [];\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 const tsconfigPath = path.join(packagesDir, pkgDir.name, 'tsconfig.json');\n\n if (fs.existsSync(packageJsonPath) && fs.existsSync(tsconfigPath)) {\n const pkgJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));\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({\n name: pkgJson.name,\n dir: path.dirname(packageJsonPath),\n tsconfigPath,\n });\n }\n }\n }\n\n return packages;\n}\n\n/**\n * Create TypeScript program for a package\n */\nfunction createProgram(packageDir: string, tsconfigPath: string): ts.Program | null {\n try {\n const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\n if (configFile.error) {return null;}\n\n const parsedConfig = ts.parseJsonConfigFileContent(\n configFile.config,\n ts.sys,\n packageDir\n );\n\n if (parsedConfig.errors.length > 0) {return null;}\n\n return ts.createProgram({\n rootNames: parsedConfig.fileNames,\n options: parsedConfig.options,\n });\n } catch (err) {\n return null;\n }\n}\n\n/**\n * Analyze type errors in a package\n */\nfunction analyzePackageTypes(program: ts.Program): { errors: number; warnings: number } {\n const diagnostics = ts.getPreEmitDiagnostics(program);\n\n let errors = 0;\n let warnings = 0;\n\n for (const diagnostic of diagnostics) {\n if (!diagnostic.file) {continue;}\n\n if (diagnostic.category === ts.DiagnosticCategory.Error) {\n errors++;\n } else {\n warnings++;\n }\n }\n\n return { errors, warnings };\n}\n\n/**\n * Calculate type coverage (simplified version)\n */\nfunction calculateTypeCoverage(program: ts.Program): {\n coverage: number;\n totalSymbols: number;\n typedSymbols: number;\n anyCount: number;\n tsIgnoreCount: number;\n} {\n const checker = program.getTypeChecker();\n const sourceFiles = program.getSourceFiles().filter(\n (sf) => !sf.isDeclarationFile && !sf.fileName.includes('node_modules')\n );\n\n let totalSymbols = 0;\n let typedSymbols = 0;\n let anyCount = 0;\n let tsIgnoreCount = 0;\n\n for (const sourceFile of sourceFiles) {\n // Count @ts-ignore comments\n const text = sourceFile.getFullText();\n const tsIgnoreMatches = text.match(/@ts-ignore/g);\n tsIgnoreCount += tsIgnoreMatches ? tsIgnoreMatches.length : 0;\n\n // Simplified type counting (focus on type nodes)\n ts.forEachChild(sourceFile, function visit(node) {\n if (ts.isTypeNode(node)) {\n totalSymbols++;\n const type = checker.getTypeAtLocation(node);\n\n if (type.flags & ts.TypeFlags.Any) {\n anyCount++;\n } else {\n typedSymbols++;\n }\n }\n\n ts.forEachChild(node, visit);\n });\n }\n\n const coverage = totalSymbols > 0 ? (typedSymbols / totalSymbols) * 100 : 100;\n\n return {\n coverage: Math.round(coverage * 10) / 10,\n totalSymbols,\n typedSymbols,\n anyCount,\n tsIgnoreCount,\n };\n}\n\n/**\n * Analyze types across monorepo\n */\nexport async function analyzeTypes(\n rootDir: string,\n options: TypeAnalysisOptions = {}\n): Promise<TypeAnalysisResult> {\n const startTime = Date.now();\n const packages = findPackagesWithTsConfig(rootDir, options.packageFilter);\n\n const result: TypeAnalysisResult = {\n totalPackages: packages.length,\n packagesWithErrors: 0,\n totalErrors: 0,\n totalWarnings: 0,\n avgCoverage: 0,\n packages: [],\n duration: 0,\n };\n\n for (const { name, dir, tsconfigPath } of packages) {\n const program = createProgram(dir, tsconfigPath);\n if (!program) {\n // Skip packages with invalid tsconfig\n continue;\n }\n\n const { errors, warnings } = analyzePackageTypes(program);\n const coverage = calculateTypeCoverage(program);\n\n result.totalErrors += errors;\n result.totalWarnings += warnings;\n\n if (errors > 0) {\n result.packagesWithErrors++;\n }\n\n result.packages.push({\n name,\n errors,\n warnings,\n coverage: coverage.coverage,\n anyCount: coverage.anyCount,\n tsIgnoreCount: coverage.tsIgnoreCount,\n });\n }\n\n // Calculate average coverage\n if (result.packages.length > 0) {\n result.avgCoverage =\n result.packages.reduce((sum, p) => sum + p.coverage, 0) / result.packages.length;\n result.avgCoverage = Math.round(result.avgCoverage * 10) / 10;\n }\n\n result.duration = Date.now() - startTime;\n\n return result;\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,90 @@
1
+ {
2
+ "name": "@kb-labs/quality-core",
3
+ "version": "0.6.0",
4
+ "type": "module",
5
+ "description": "Core business logic for KB Labs Quality plugin - monorepo analysis, dependency management, and health checks.",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./stats": {
14
+ "import": "./dist/stats/index.js",
15
+ "types": "./dist/stats/index.d.ts"
16
+ },
17
+ "./health": {
18
+ "import": "./dist/health/index.js",
19
+ "types": "./dist/health/index.d.ts"
20
+ },
21
+ "./dependencies": {
22
+ "import": "./dist/dependencies/index.js",
23
+ "types": "./dist/dependencies/index.d.ts"
24
+ },
25
+ "./build-order": {
26
+ "import": "./dist/build-order/index.js",
27
+ "types": "./dist/build-order/index.d.ts"
28
+ },
29
+ "./graph": {
30
+ "import": "./dist/graph/index.js",
31
+ "types": "./dist/graph/index.d.ts"
32
+ },
33
+ "./stale": {
34
+ "import": "./dist/stale/index.js",
35
+ "types": "./dist/stale/index.d.ts"
36
+ },
37
+ "./builds": {
38
+ "import": "./dist/builds/index.js",
39
+ "types": "./dist/builds/index.d.ts"
40
+ },
41
+ "./types": {
42
+ "import": "./dist/types/index.js",
43
+ "types": "./dist/types/index.d.ts"
44
+ },
45
+ "./tests": {
46
+ "import": "./dist/tests/index.js",
47
+ "types": "./dist/tests/index.d.ts"
48
+ },
49
+ "./dead-code": {
50
+ "import": "./dist/dead-code/index.js",
51
+ "types": "./dist/dead-code/index.d.ts"
52
+ },
53
+ "./dist/*": "./dist/*"
54
+ },
55
+ "files": [
56
+ "dist",
57
+ "README.md",
58
+ "LICENSE"
59
+ ],
60
+ "sideEffects": false,
61
+ "scripts": {
62
+ "pretype-check": "pnpm --filter @kb-labs/quality-core build",
63
+ "clean": "rimraf dist",
64
+ "build": "tsup --config tsup.config.ts",
65
+ "dev": "tsup --config tsup.config.ts --watch",
66
+ "lint": "eslint src --ext .ts",
67
+ "lint:fix": "eslint . --fix",
68
+ "type-check": "tsc --noEmit",
69
+ "test": "vitest run --passWithNoTests",
70
+ "test:watch": "vitest"
71
+ },
72
+ "dependencies": {
73
+ "@kb-labs/quality-contracts": "^0.6.0",
74
+ "@kb-labs/sdk": "^1.5.0",
75
+ "globby": "^11.0.0",
76
+ "minimatch": "^10.0.1",
77
+ "typescript": "^5.6.3"
78
+ },
79
+ "devDependencies": {
80
+ "@kb-labs/devkit": "link:../../../../infra/kb-labs-devkit",
81
+ "@types/node": "^24.3.3",
82
+ "rimraf": "^6.0.1",
83
+ "tsup": "^8.5.0",
84
+ "vitest": "^3.2.4"
85
+ },
86
+ "engines": {
87
+ "node": ">=20.0.0",
88
+ "pnpm": ">=9.0.0"
89
+ }
90
+ }