@kb-labs/quality-core 2.93.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/graph/dependency-graph.ts","../../src/stale/analyze-stale.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Dependency graph utilities for monorepo analysis\n *\n * Provides topological sorting, circular dependency detection,\n * and build order calculation based on workspace dependencies.\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nexport interface PackageNode {\n name: string;\n path: string;\n dir: string;\n deps: Set<string>;\n devDeps?: Set<string>;\n dependents: Set<string>; // Packages that depend on this one\n}\n\nexport interface DependencyGraph {\n nodes: Map<string, PackageNode>;\n workspacePackages: Set<string>;\n}\n\nexport interface TopologicalSortResult {\n layers: string[][]; // Build layers - each layer can build in parallel\n sorted: string[]; // Flattened topological order\n circular: string[][]; // Circular dependency chains\n}\n\n/**\n * Build dependency graph from monorepo packages\n */\nexport function buildDependencyGraph(rootDir: string): DependencyGraph {\n const nodes = new Map<string, PackageNode>();\n const workspacePackages = new Set<string>();\n\n if (!fs.existsSync(rootDir)) {\n return { nodes, workspacePackages };\n }\n\n const entries = fs.readdirSync(rootDir, { withFileTypes: true });\n\n // First pass: collect all workspace packages\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 const packageName = pkgJson.name;\n\n if (packageName) {\n workspacePackages.add(packageName);\n }\n }\n }\n }\n\n // Second pass: build dependency graph\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 const packageName = pkgJson.name;\n\n if (!packageName) {continue;}\n\n const deps = new Set<string>();\n const devDeps = new Set<string>();\n\n // Collect workspace dependencies\n const allDeps = {\n ...pkgJson.dependencies,\n ...pkgJson.devDependencies,\n };\n\n for (const dep of Object.keys(allDeps)) {\n if (workspacePackages.has(dep)) {\n deps.add(dep);\n }\n }\n\n // Separate dev dependencies\n if (pkgJson.devDependencies) {\n for (const dep of Object.keys(pkgJson.devDependencies)) {\n if (workspacePackages.has(dep)) {\n devDeps.add(dep);\n }\n }\n }\n\n nodes.set(packageName, {\n name: packageName,\n path: packageJsonPath,\n dir: path.dirname(packageJsonPath),\n deps,\n devDeps,\n dependents: new Set(),\n });\n }\n }\n }\n\n // Second pass: calculate reverse dependencies (dependents)\n for (const [packageName, node] of nodes) {\n for (const dep of node.deps) {\n const depNode = nodes.get(dep);\n if (depNode) {\n depNode.dependents.add(packageName);\n }\n }\n }\n\n return { nodes, workspacePackages };\n}\n\n/**\n * Topological sort using Kahn's algorithm\n * Returns build layers where each layer can be built in parallel\n */\nexport function topologicalSort(graph: DependencyGraph): TopologicalSortResult {\n const { nodes } = graph;\n const layers: string[][] = [];\n const sorted: string[] = [];\n const circular: string[][] = [];\n\n // Calculate in-degree for each node\n const inDegree = new Map<string, number>();\n for (const [name] of nodes) {\n inDegree.set(name, 0);\n }\n\n for (const [, node] of nodes) {\n for (const dep of node.deps) {\n if (nodes.has(dep)) {\n inDegree.set(dep, (inDegree.get(dep) ?? 0) + 1);\n }\n }\n }\n\n // Kahn's algorithm with layers\n const queue: string[] = [];\n for (const [name, degree] of inDegree) {\n if (degree === 0) {\n queue.push(name);\n }\n }\n\n while (queue.length > 0) {\n const layer: string[] = [...queue];\n layers.push(layer);\n sorted.push(...layer);\n queue.length = 0;\n\n for (const name of layer) {\n const node = nodes.get(name);\n if (!node) {continue;}\n\n for (const dep of node.deps) {\n if (nodes.has(dep)) {\n const newDegree = (inDegree.get(dep) ?? 0) - 1;\n inDegree.set(dep, newDegree);\n if (newDegree === 0) {\n queue.push(dep);\n }\n }\n }\n }\n }\n\n // Check for circular dependencies\n if (sorted.length < nodes.size) {\n const remaining = new Set<string>();\n for (const [name] of nodes) {\n if (!sorted.includes(name)) {\n remaining.add(name);\n }\n }\n\n // Find cycles using DFS\n const cycles = findCircularDependencies(graph, remaining);\n circular.push(...cycles);\n }\n\n return { layers, sorted, circular };\n}\n\n/**\n * Find circular dependencies using DFS\n */\nexport function findCircularDependencies(\n graph: DependencyGraph,\n subset?: Set<string>\n): string[][] {\n const { nodes } = graph;\n const visited = new Set<string>();\n const recStack = new Set<string>();\n const cycles: string[][] = [];\n\n function dfs(node: string, path: string[]): void {\n visited.add(node);\n recStack.add(node);\n path.push(node);\n\n const nodeData = nodes.get(node);\n if (!nodeData) {return;}\n\n for (const dep of nodeData.deps) {\n if (!nodes.has(dep)) {continue;}\n if (subset && !subset.has(dep)) {continue;}\n\n if (!visited.has(dep)) {\n dfs(dep, [...path]);\n } else if (recStack.has(dep)) {\n // Found cycle\n const cycleStart = path.indexOf(dep);\n if (cycleStart !== -1) {\n const cycle = path.slice(cycleStart);\n cycles.push([...cycle, dep]);\n }\n }\n }\n\n recStack.delete(node);\n }\n\n const nodesToCheck = subset ?? new Set(nodes.keys());\n for (const node of nodesToCheck) {\n if (!visited.has(node)) {\n dfs(node, []);\n }\n }\n\n return cycles;\n}\n\n/**\n * Get build order for a specific package and its dependencies\n */\nexport function getBuildOrderForPackage(\n graph: DependencyGraph,\n packageName: string\n): TopologicalSortResult {\n const { nodes } = graph;\n\n // Find all dependencies recursively\n const allDeps = new Set<string>();\n const queue = [packageName];\n\n while (queue.length > 0) {\n const current = queue.shift()!;\n allDeps.add(current);\n\n const node = nodes.get(current);\n if (!node) {continue;}\n\n for (const dep of node.deps) {\n if (!allDeps.has(dep) && nodes.has(dep)) {\n queue.push(dep);\n }\n }\n }\n\n // Create subgraph with only relevant packages\n const subgraph: DependencyGraph = {\n nodes: new Map(),\n workspacePackages: graph.workspacePackages,\n };\n\n for (const pkg of allDeps) {\n const node = nodes.get(pkg);\n if (node) {\n subgraph.nodes.set(pkg, node);\n }\n }\n\n return topologicalSort(subgraph);\n}\n\n/**\n * Reverse dependency graph - who depends on this package\n */\nexport function getReverseDependencies(\n graph: DependencyGraph,\n packageName: string\n): Set<string> {\n const { nodes } = graph;\n const reverseDeps = new Set<string>();\n\n for (const [name, node] of nodes) {\n if (node.deps.has(packageName)) {\n reverseDeps.add(name);\n }\n }\n\n return reverseDeps;\n}\n\n/**\n * Get impact analysis - all packages affected by changes to this package\n */\nexport function getImpactAnalysis(\n graph: DependencyGraph,\n packageName: string\n): Set<string> {\n const affected = new Set<string>();\n const queue = [packageName];\n\n while (queue.length > 0) {\n const current = queue.shift()!;\n const reverseDeps = getReverseDependencies(graph, current);\n\n for (const dep of reverseDeps) {\n if (!affected.has(dep)) {\n affected.add(dep);\n queue.push(dep);\n }\n }\n }\n\n return affected;\n}\n","/**\n * Stale packages detection\n * Finds packages that need rebuilding due to source changes or dependency updates\n */\n\nimport { readFile, stat, access } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport globby from 'globby';\nimport { buildDependencyGraph } from '../graph/index.js';\n\nexport interface StalePackage {\n name: string;\n path: string;\n reason: 'source-newer' | 'dependency-rebuilt' | 'missing-dist';\n sourceModified?: string;\n distModified?: string;\n affectedPackages: string[];\n affectedCount: number;\n severity: 'critical' | 'high' | 'medium' | 'low';\n}\n\nexport interface StaleChain {\n root: string;\n affected: string[];\n depth: number;\n}\n\nexport interface StaleAnalysis {\n stalePackages: StalePackage[];\n totalStale: number;\n totalAffected: number;\n criticalChains: StaleChain[];\n}\n\n/**\n * Get latest modification time from a list of files\n */\nasync function getLatestModTime(files: string[]): Promise<Date | null> {\n if (files.length === 0) {return null;}\n\n let latest: Date | null = null;\n\n for (const file of files) {\n try {\n const stats = await stat(file);\n if (!latest || stats.mtime > latest) {\n latest = stats.mtime;\n }\n } catch {\n // Skip files that can't be accessed\n }\n }\n\n return latest;\n}\n\n/**\n * Check if dist directory exists\n */\nasync function distExists(packagePath: string): Promise<boolean> {\n try {\n await access(join(packagePath, 'dist'));\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Analyze a single package for staleness\n */\nasync function analyzePackage(\n packagePath: string,\n packageJson: Record<string, unknown>,\n graph: ReturnType<typeof buildDependencyGraph>\n): Promise<StalePackage | null> {\n const name = packageJson.name as string;\n\n // Check if dist exists\n const hasDistCheck = await distExists(packagePath);\n if (!hasDistCheck) {\n // Get affected packages\n const node = graph.nodes.get(name);\n const affectedPackages = node ? Array.from(node.dependents) : [];\n\n return {\n name,\n path: packagePath,\n reason: 'missing-dist',\n affectedPackages,\n affectedCount: affectedPackages.length,\n severity: affectedPackages.length > 20 ? 'critical' : affectedPackages.length > 10 ? 'high' : 'medium',\n };\n }\n\n // Get source files\n const srcFiles = await globby('src/**/*.{ts,tsx,js,jsx}', {\n cwd: packagePath,\n absolute: true,\n ignore: ['**/*.test.ts', '**/*.spec.ts', '**/__tests__/**'],\n });\n\n // Get dist files\n const distFiles = await globby('dist/**/*.{js,mjs,cjs,d.ts}', {\n cwd: packagePath,\n absolute: true,\n });\n\n const srcModTime = await getLatestModTime(srcFiles);\n const distModTime = await getLatestModTime(distFiles);\n\n // Check if source is newer than dist\n if (srcModTime && distModTime && srcModTime > distModTime) {\n const node = graph.nodes.get(name);\n const affectedPackages = node ? Array.from(node.dependents) : [];\n\n return {\n name,\n path: packagePath,\n reason: 'source-newer',\n sourceModified: srcModTime.toISOString(),\n distModified: distModTime.toISOString(),\n affectedPackages,\n affectedCount: affectedPackages.length,\n severity: affectedPackages.length > 20 ? 'critical' : affectedPackages.length > 10 ? 'high' : affectedPackages.length > 5 ? 'medium' : 'low',\n };\n }\n\n // Check if dependencies were rebuilt after this package\n if (distModTime) {\n const deps = packageJson.dependencies || {};\n const node = graph.nodes.get(name);\n\n if (node) {\n for (const depName of node.deps) {\n const depNode = graph.nodes.get(depName);\n if (depNode) {\n // Find dep package.json to get its dist time\n const depPackages = await globby('**/package.json', {\n cwd: join(packagePath, '../..'), // Go up to monorepo root\n absolute: true,\n ignore: ['**/node_modules/**'],\n });\n\n for (const depPkgPath of depPackages) {\n try {\n const depPkg = JSON.parse(await readFile(depPkgPath, 'utf-8'));\n if (depPkg.name === depName) {\n const depDir = join(depPkgPath, '..');\n const depDistFiles = await globby('dist/**/*.{js,mjs,cjs}', {\n cwd: depDir,\n absolute: true,\n });\n const depDistModTime = await getLatestModTime(depDistFiles);\n\n if (depDistModTime && depDistModTime > distModTime) {\n const affectedPackages = node ? Array.from(node.dependents) : [];\n\n return {\n name,\n path: packagePath,\n reason: 'dependency-rebuilt',\n sourceModified: srcModTime?.toISOString(),\n distModified: distModTime.toISOString(),\n affectedPackages,\n affectedCount: affectedPackages.length,\n severity: affectedPackages.length > 20 ? 'critical' : affectedPackages.length > 10 ? 'high' : affectedPackages.length > 5 ? 'medium' : 'low',\n };\n }\n }\n } catch {\n // Skip invalid package.json\n }\n }\n }\n }\n }\n }\n\n return null;\n}\n\n/**\n * Calculate max depth from a package in dependency graph\n */\nfunction getMaxDepth(\n graph: ReturnType<typeof buildDependencyGraph>,\n packageName: string,\n visited = new Set<string>()\n): number {\n if (visited.has(packageName)) {return 0;}\n visited.add(packageName);\n\n const node = graph.nodes.get(packageName);\n if (!node || node.dependents.size === 0) {return 0;}\n\n let maxDepth = 0;\n for (const dependent of node.dependents) {\n const depth = 1 + getMaxDepth(graph, dependent, new Set(visited));\n if (depth > maxDepth) {maxDepth = depth;}\n }\n\n return maxDepth;\n}\n\n/**\n * Analyze all packages for staleness\n */\nexport async function analyzeStalePackages(\n rootDir: string,\n detailed = false\n): Promise<StaleAnalysis> {\n // Build dependency graph first\n const graph = buildDependencyGraph(rootDir);\n\n // Find all package.json files (depth=6 covers workspace/subrepo/packages/pkg/...)\n const packageJsonFiles = await globby('**/package.json', {\n cwd: rootDir,\n ignore: ['**/node_modules/**', '**/.git/**', '**/.kb/**', '**/dist/**', 'package.json'],\n absolute: true,\n deep: 6,\n });\n\n // Analyze packages in parallel\n const results = await Promise.all(\n packageJsonFiles.map(async (pkgPath) => {\n try {\n const content = await readFile(pkgPath, 'utf-8');\n const pkg = JSON.parse(content);\n const packagePath = join(pkgPath, '..');\n return await analyzePackage(packagePath, pkg, graph);\n } catch {\n return null;\n }\n })\n );\n\n const stalePackages: StalePackage[] = results.filter((r): r is StalePackage => r !== null);\n\n // Calculate total affected (unique packages)\n const allAffected = new Set<string>();\n for (const stalePkg of stalePackages) {\n for (const affected of stalePkg.affectedPackages) {\n allAffected.add(affected);\n }\n }\n\n // Find critical chains (if detailed)\n let criticalChains: StaleChain[] = [];\n if (detailed) {\n criticalChains = stalePackages\n .filter(pkg => pkg.affectedCount > 5)\n .map(pkg => ({\n root: pkg.name,\n affected: pkg.affectedPackages,\n depth: getMaxDepth(graph, pkg.name),\n }))\n .sort((a, b) => b.affected.length - a.affected.length)\n .slice(0, 10); // Top 10 critical chains\n }\n\n return {\n stalePackages: stalePackages.sort((a, b) => b.affectedCount - a.affectedCount),\n totalStale: stalePackages.length,\n totalAffected: allAffected.size,\n criticalChains,\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/graph/dependency-graph.ts","../../src/stale/analyze-stale.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Dependency graph utilities for monorepo analysis\n *\n * Provides topological sorting, circular dependency detection,\n * and build order calculation based on workspace dependencies.\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nexport interface PackageNode {\n name: string;\n path: string;\n dir: string;\n deps: Set<string>;\n devDeps?: Set<string>;\n dependents: Set<string>; // Packages that depend on this one\n}\n\nexport interface DependencyGraph {\n nodes: Map<string, PackageNode>;\n workspacePackages: Set<string>;\n}\n\nexport interface TopologicalSortResult {\n layers: string[][]; // Build layers - each layer can build in parallel\n sorted: string[]; // Flattened topological order\n circular: string[][]; // Circular dependency chains\n}\n\n/**\n * Build dependency graph from monorepo packages\n */\nexport function buildDependencyGraph(rootDir: string): DependencyGraph {\n const nodes = new Map<string, PackageNode>();\n const workspacePackages = new Set<string>();\n\n if (!fs.existsSync(rootDir)) {\n return { nodes, workspacePackages };\n }\n\n const entries = fs.readdirSync(rootDir, { withFileTypes: true });\n\n // First pass: collect all workspace packages\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 const packageName = pkgJson.name;\n\n if (packageName) {\n workspacePackages.add(packageName);\n }\n }\n }\n }\n\n // Second pass: build dependency graph\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 const packageName = pkgJson.name;\n\n if (!packageName) {continue;}\n\n const deps = new Set<string>();\n const devDeps = new Set<string>();\n\n // Collect workspace dependencies\n const allDeps = {\n ...pkgJson.dependencies,\n ...pkgJson.devDependencies,\n };\n\n for (const dep of Object.keys(allDeps)) {\n if (workspacePackages.has(dep)) {\n deps.add(dep);\n }\n }\n\n // Separate dev dependencies\n if (pkgJson.devDependencies) {\n for (const dep of Object.keys(pkgJson.devDependencies)) {\n if (workspacePackages.has(dep)) {\n devDeps.add(dep);\n }\n }\n }\n\n nodes.set(packageName, {\n name: packageName,\n path: packageJsonPath,\n dir: path.dirname(packageJsonPath),\n deps,\n devDeps,\n dependents: new Set(),\n });\n }\n }\n }\n\n // Second pass: calculate reverse dependencies (dependents)\n for (const [packageName, node] of nodes) {\n for (const dep of node.deps) {\n const depNode = nodes.get(dep);\n if (depNode) {\n depNode.dependents.add(packageName);\n }\n }\n }\n\n return { nodes, workspacePackages };\n}\n\n/**\n * Topological sort using Kahn's algorithm\n * Returns build layers where each layer can be built in parallel\n */\nexport function topologicalSort(graph: DependencyGraph): TopologicalSortResult {\n const { nodes } = graph;\n const layers: string[][] = [];\n const sorted: string[] = [];\n const circular: string[][] = [];\n\n // Calculate in-degree for each node\n const inDegree = new Map<string, number>();\n for (const [name] of nodes) {\n inDegree.set(name, 0);\n }\n\n for (const [, node] of nodes) {\n for (const dep of node.deps) {\n if (nodes.has(dep)) {\n inDegree.set(dep, (inDegree.get(dep) ?? 0) + 1);\n }\n }\n }\n\n // Kahn's algorithm with layers\n const queue: string[] = [];\n for (const [name, degree] of inDegree) {\n if (degree === 0) {\n queue.push(name);\n }\n }\n\n while (queue.length > 0) {\n const layer: string[] = [...queue];\n layers.push(layer);\n sorted.push(...layer);\n queue.length = 0;\n\n for (const name of layer) {\n const node = nodes.get(name);\n if (!node) {continue;}\n\n for (const dep of node.deps) {\n if (nodes.has(dep)) {\n const newDegree = (inDegree.get(dep) ?? 0) - 1;\n inDegree.set(dep, newDegree);\n if (newDegree === 0) {\n queue.push(dep);\n }\n }\n }\n }\n }\n\n // Check for circular dependencies\n if (sorted.length < nodes.size) {\n const remaining = new Set<string>();\n for (const [name] of nodes) {\n if (!sorted.includes(name)) {\n remaining.add(name);\n }\n }\n\n // Find cycles using DFS\n const cycles = findCircularDependencies(graph, remaining);\n circular.push(...cycles);\n }\n\n return { layers, sorted, circular };\n}\n\n/**\n * Find circular dependencies using DFS\n */\nexport function findCircularDependencies(\n graph: DependencyGraph,\n subset?: Set<string>\n): string[][] {\n const { nodes } = graph;\n const visited = new Set<string>();\n const recStack = new Set<string>();\n const cycles: string[][] = [];\n\n function dfs(node: string, path: string[]): void {\n visited.add(node);\n recStack.add(node);\n path.push(node);\n\n const nodeData = nodes.get(node);\n if (!nodeData) {return;}\n\n for (const dep of nodeData.deps) {\n if (!nodes.has(dep)) {continue;}\n if (subset && !subset.has(dep)) {continue;}\n\n if (!visited.has(dep)) {\n dfs(dep, [...path]);\n } else if (recStack.has(dep)) {\n // Found cycle\n const cycleStart = path.indexOf(dep);\n if (cycleStart !== -1) {\n const cycle = path.slice(cycleStart);\n cycles.push([...cycle, dep]);\n }\n }\n }\n\n recStack.delete(node);\n }\n\n const nodesToCheck = subset ?? new Set(nodes.keys());\n for (const node of nodesToCheck) {\n if (!visited.has(node)) {\n dfs(node, []);\n }\n }\n\n return cycles;\n}\n\n/**\n * Get build order for a specific package and its dependencies\n */\nexport function getBuildOrderForPackage(\n graph: DependencyGraph,\n packageName: string\n): TopologicalSortResult {\n const { nodes } = graph;\n\n // Find all dependencies recursively\n const allDeps = new Set<string>();\n const queue = [packageName];\n\n while (queue.length > 0) {\n const current = queue.shift()!;\n allDeps.add(current);\n\n const node = nodes.get(current);\n if (!node) {continue;}\n\n for (const dep of node.deps) {\n if (!allDeps.has(dep) && nodes.has(dep)) {\n queue.push(dep);\n }\n }\n }\n\n // Create subgraph with only relevant packages\n const subgraph: DependencyGraph = {\n nodes: new Map(),\n workspacePackages: graph.workspacePackages,\n };\n\n for (const pkg of allDeps) {\n const node = nodes.get(pkg);\n if (node) {\n subgraph.nodes.set(pkg, node);\n }\n }\n\n return topologicalSort(subgraph);\n}\n\n/**\n * Reverse dependency graph - who depends on this package\n */\nexport function getReverseDependencies(\n graph: DependencyGraph,\n packageName: string\n): Set<string> {\n const { nodes } = graph;\n const reverseDeps = new Set<string>();\n\n for (const [name, node] of nodes) {\n if (node.deps.has(packageName)) {\n reverseDeps.add(name);\n }\n }\n\n return reverseDeps;\n}\n\n/**\n * Get impact analysis - all packages affected by changes to this package\n */\nexport function getImpactAnalysis(\n graph: DependencyGraph,\n packageName: string\n): Set<string> {\n const affected = new Set<string>();\n const queue = [packageName];\n\n while (queue.length > 0) {\n const current = queue.shift()!;\n const reverseDeps = getReverseDependencies(graph, current);\n\n for (const dep of reverseDeps) {\n if (!affected.has(dep)) {\n affected.add(dep);\n queue.push(dep);\n }\n }\n }\n\n return affected;\n}\n","/**\n * Stale packages detection\n * Finds packages that need rebuilding due to source changes or dependency updates\n */\n\nimport { readFile, stat, access } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport globby from 'globby';\nimport { buildDependencyGraph } from '../graph/index.js';\n\nexport interface StalePackage {\n name: string;\n path: string;\n reason: 'source-newer' | 'dependency-rebuilt' | 'missing-dist';\n sourceModified?: string;\n distModified?: string;\n affectedPackages: string[];\n affectedCount: number;\n severity: 'critical' | 'high' | 'medium' | 'low';\n}\n\nexport interface StaleChain {\n root: string;\n affected: string[];\n depth: number;\n}\n\nexport interface StaleAnalysis {\n stalePackages: StalePackage[];\n totalStale: number;\n totalAffected: number;\n criticalChains: StaleChain[];\n}\n\n/**\n * Get latest modification time from a list of files\n */\nasync function getLatestModTime(files: string[]): Promise<Date | null> {\n if (files.length === 0) {return null;}\n\n let latest: Date | null = null;\n\n for (const file of files) {\n try {\n const stats = await stat(file);\n if (!latest || stats.mtime > latest) {\n latest = stats.mtime;\n }\n } catch {\n // Skip files that can't be accessed\n }\n }\n\n return latest;\n}\n\n/**\n * Check if dist directory exists\n */\nasync function distExists(packagePath: string): Promise<boolean> {\n try {\n await access(join(packagePath, 'dist'));\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Analyze a single package for staleness\n */\nasync function analyzePackage(\n packagePath: string,\n packageJson: Record<string, unknown>,\n graph: ReturnType<typeof buildDependencyGraph>\n): Promise<StalePackage | null> {\n const name = packageJson.name as string;\n\n // Check if dist exists\n const hasDistCheck = await distExists(packagePath);\n if (!hasDistCheck) {\n // Get affected packages\n const node = graph.nodes.get(name);\n const affectedPackages = node ? Array.from(node.dependents) : [];\n\n return {\n name,\n path: packagePath,\n reason: 'missing-dist',\n affectedPackages,\n affectedCount: affectedPackages.length,\n severity: affectedPackages.length > 20 ? 'critical' : affectedPackages.length > 10 ? 'high' : 'medium',\n };\n }\n\n // Get source files\n const srcFiles = await globby('src/**/*.{ts,tsx,js,jsx}', {\n cwd: packagePath,\n absolute: true,\n ignore: ['**/*.test.ts', '**/*.spec.ts', '**/__tests__/**'],\n });\n\n // Get dist files\n const distFiles = await globby('dist/**/*.{js,mjs,cjs,d.ts}', {\n cwd: packagePath,\n absolute: true,\n });\n\n const srcModTime = await getLatestModTime(srcFiles);\n const distModTime = await getLatestModTime(distFiles);\n\n // Check if source is newer than dist\n if (srcModTime && distModTime && srcModTime > distModTime) {\n const node = graph.nodes.get(name);\n const affectedPackages = node ? Array.from(node.dependents) : [];\n\n return {\n name,\n path: packagePath,\n reason: 'source-newer',\n sourceModified: srcModTime.toISOString(),\n distModified: distModTime.toISOString(),\n affectedPackages,\n affectedCount: affectedPackages.length,\n severity: affectedPackages.length > 20 ? 'critical' : affectedPackages.length > 10 ? 'high' : affectedPackages.length > 5 ? 'medium' : 'low',\n };\n }\n\n // Check if dependencies were rebuilt after this package\n if (distModTime) {\n const _deps = packageJson.dependencies || {};\n const node = graph.nodes.get(name);\n\n if (node) {\n for (const depName of node.deps) {\n const depNode = graph.nodes.get(depName);\n if (depNode) {\n // Find dep package.json to get its dist time\n const depPackages = await globby('**/package.json', {\n cwd: join(packagePath, '../..'), // Go up to monorepo root\n absolute: true,\n ignore: ['**/node_modules/**'],\n });\n\n for (const depPkgPath of depPackages) {\n try {\n const depPkg = JSON.parse(await readFile(depPkgPath, 'utf-8'));\n if (depPkg.name === depName) {\n const depDir = join(depPkgPath, '..');\n const depDistFiles = await globby('dist/**/*.{js,mjs,cjs}', {\n cwd: depDir,\n absolute: true,\n });\n const depDistModTime = await getLatestModTime(depDistFiles);\n\n if (depDistModTime && depDistModTime > distModTime) {\n const affectedPackages = node ? Array.from(node.dependents) : [];\n\n return {\n name,\n path: packagePath,\n reason: 'dependency-rebuilt',\n sourceModified: srcModTime?.toISOString(),\n distModified: distModTime.toISOString(),\n affectedPackages,\n affectedCount: affectedPackages.length,\n severity: affectedPackages.length > 20 ? 'critical' : affectedPackages.length > 10 ? 'high' : affectedPackages.length > 5 ? 'medium' : 'low',\n };\n }\n }\n } catch {\n // Skip invalid package.json\n }\n }\n }\n }\n }\n }\n\n return null;\n}\n\n/**\n * Calculate max depth from a package in dependency graph\n */\nfunction getMaxDepth(\n graph: ReturnType<typeof buildDependencyGraph>,\n packageName: string,\n visited = new Set<string>()\n): number {\n if (visited.has(packageName)) {return 0;}\n visited.add(packageName);\n\n const node = graph.nodes.get(packageName);\n if (!node || node.dependents.size === 0) {return 0;}\n\n let maxDepth = 0;\n for (const dependent of node.dependents) {\n const depth = 1 + getMaxDepth(graph, dependent, new Set(visited));\n if (depth > maxDepth) {maxDepth = depth;}\n }\n\n return maxDepth;\n}\n\n/**\n * Analyze all packages for staleness\n */\nexport async function analyzeStalePackages(\n rootDir: string,\n detailed = false\n): Promise<StaleAnalysis> {\n // Build dependency graph first\n const graph = buildDependencyGraph(rootDir);\n\n // Find all package.json files (depth=6 covers workspace/subrepo/packages/pkg/...)\n const packageJsonFiles = await globby('**/package.json', {\n cwd: rootDir,\n ignore: ['**/node_modules/**', '**/.git/**', '**/.kb/**', '**/dist/**', 'package.json'],\n absolute: true,\n deep: 6,\n });\n\n // Analyze packages in parallel\n const results = await Promise.all(\n packageJsonFiles.map(async (pkgPath) => {\n try {\n const content = await readFile(pkgPath, 'utf-8');\n const pkg = JSON.parse(content);\n const packagePath = join(pkgPath, '..');\n return await analyzePackage(packagePath, pkg, graph);\n } catch {\n return null;\n }\n })\n );\n\n const stalePackages: StalePackage[] = results.filter((r): r is StalePackage => r !== null);\n\n // Calculate total affected (unique packages)\n const allAffected = new Set<string>();\n for (const stalePkg of stalePackages) {\n for (const affected of stalePkg.affectedPackages) {\n allAffected.add(affected);\n }\n }\n\n // Find critical chains (if detailed)\n let criticalChains: StaleChain[] = [];\n if (detailed) {\n criticalChains = stalePackages\n .filter(pkg => pkg.affectedCount > 5)\n .map(pkg => ({\n root: pkg.name,\n affected: pkg.affectedPackages,\n depth: getMaxDepth(graph, pkg.name),\n }))\n .sort((a, b) => b.affected.length - a.affected.length)\n .slice(0, 10); // Top 10 critical chains\n }\n\n return {\n stalePackages: stalePackages.sort((a, b) => b.affectedCount - a.affectedCount),\n totalStale: stalePackages.length,\n totalAffected: allAffected.size,\n criticalChains,\n };\n}\n"]}
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Calculate monorepo statistics
3
3
  *
4
- * Atomic functions for counting packages, LOC, size, etc.
4
+ * Fast path: git ls-files (index read, no FS traversal) + stat (metadata only) +
5
+ * sampling 200 files to calibrate bytes-per-line ratio for the LOC estimate.
5
6
  */
6
7
  interface MonorepoStats {
7
8
  packages: number;
@@ -9,14 +10,6 @@ interface MonorepoStats {
9
10
  size: number;
10
11
  sizeFormatted: string;
11
12
  }
12
- /**
13
- * Calculate total lines of code in all source files
14
- */
15
- declare function calculateLinesOfCode(rootDir: string, sourceFiles?: string[]): Promise<number>;
16
- /**
17
- * Calculate total size of source files in bytes
18
- */
19
- declare function calculateSize(rootDir: string, sourceFiles?: string[]): Promise<number>;
20
13
  /**
21
14
  * Format bytes to human-readable string
22
15
  */
@@ -29,5 +22,7 @@ declare function countPackages(rootDir: string): Promise<number>;
29
22
  * Calculate all stats at once
30
23
  */
31
24
  declare function calculateStats(rootDir: string): Promise<MonorepoStats>;
25
+ declare function calculateLinesOfCode(rootDir: string): Promise<number>;
26
+ declare function calculateSize(rootDir: string): Promise<number>;
32
27
 
33
28
  export { type MonorepoStats, calculateLinesOfCode, calculateSize, calculateStats, countPackages, formatBytes };
@@ -1,34 +1,57 @@
1
- import { readFile, stat } from 'fs/promises';
1
+ import { stat, readFile } from 'fs/promises';
2
+ import { execFile } from 'child_process';
3
+ import path from 'path';
2
4
  import globby from 'globby';
3
5
 
4
6
  // src/stats/calculate-stats.ts
5
- async function calculateLinesOfCode(rootDir, sourceFiles) {
6
- const files = sourceFiles ?? await globby("**/*.{ts,tsx,js,jsx}", {
7
- cwd: rootDir,
8
- ignore: [
9
- "**/node_modules/**",
10
- "**/dist/**",
11
- "**/.git/**",
12
- "**/.kb/**",
13
- "**/build/**",
14
- "**/*.test.{ts,tsx,js,jsx}",
15
- "**/*.spec.{ts,tsx,js,jsx}"
16
- ],
17
- absolute: true,
18
- deep: 8
19
- });
20
- const contents = await Promise.all(files.map((f) => readFile(f, "utf-8").catch(() => null)));
21
- return contents.reduce((sum, c) => sum + (c ? c.split("\n").length : 0), 0);
7
+ var LOC_SAMPLE_SIZE = 200;
8
+ var FALLBACK_BYTES_PER_LINE = 45;
9
+ async function readTextFile(f) {
10
+ try {
11
+ return await readFile(f, "utf8");
12
+ } catch {
13
+ return null;
14
+ }
22
15
  }
23
- async function calculateSize(rootDir, sourceFiles) {
24
- const files = sourceFiles ?? await globby("**/*.{ts,tsx,js,jsx}", {
25
- cwd: rootDir,
26
- ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**", "**/.kb/**", "**/build/**"],
27
- absolute: true,
28
- deep: 8
16
+ var SOURCE_PATTERNS = ["*.ts", "*.tsx", "*.js", "*.jsx", "*.go"];
17
+ async function getSourceFiles(rootDir) {
18
+ return new Promise((resolve) => {
19
+ execFile(
20
+ "git",
21
+ ["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...SOURCE_PATTERNS],
22
+ { cwd: rootDir, maxBuffer: 64 * 1024 * 1024 },
23
+ (err, stdout) => {
24
+ if (err || !stdout) {
25
+ return resolve([]);
26
+ }
27
+ resolve(
28
+ stdout.split("\0").filter(Boolean).map((f) => path.join(rootDir, f))
29
+ );
30
+ }
31
+ );
29
32
  });
30
- const stats = await Promise.all(files.map((f) => stat(f).catch(() => null)));
31
- return stats.reduce((sum, s) => sum + (s ? s.size : 0), 0);
33
+ }
34
+ async function estimateLoc(files, totalSize) {
35
+ if (files.length === 0) {
36
+ return 0;
37
+ }
38
+ const indices = /* @__PURE__ */ new Set();
39
+ while (indices.size < Math.min(LOC_SAMPLE_SIZE, files.length)) {
40
+ indices.add(Math.floor(Math.random() * files.length));
41
+ }
42
+ const sample = Array.from(indices).map((i) => files[i]).filter((f) => f !== void 0);
43
+ const contents = await Promise.all(sample.map(readTextFile));
44
+ let sampleBytes = 0;
45
+ let sampleLines = 0;
46
+ for (const c of contents) {
47
+ if (!c) {
48
+ continue;
49
+ }
50
+ sampleBytes += Buffer.byteLength(c, "utf-8");
51
+ sampleLines += c.split("\n").length;
52
+ }
53
+ const bytesPerLine = sampleLines > 0 ? sampleBytes / sampleLines : FALLBACK_BYTES_PER_LINE;
54
+ return Math.round(totalSize / bytesPerLine);
32
55
  }
33
56
  function formatBytes(bytes) {
34
57
  if (bytes === 0) {
@@ -50,17 +73,13 @@ async function countPackages(rootDir) {
50
73
  return packageJsonFiles.filter((p) => p !== "package.json").length;
51
74
  }
52
75
  async function calculateStats(rootDir) {
53
- const sourceFiles = await globby("**/*.{ts,tsx,js,jsx}", {
54
- cwd: rootDir,
55
- ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**", "**/.kb/**", "**/build/**"],
56
- absolute: true,
57
- deep: 8
58
- });
59
- const [packages, loc, size] = await Promise.all([
60
- countPackages(rootDir),
61
- calculateLinesOfCode(rootDir, sourceFiles),
62
- calculateSize(rootDir, sourceFiles)
76
+ const [files, packages] = await Promise.all([
77
+ getSourceFiles(rootDir),
78
+ countPackages(rootDir)
63
79
  ]);
80
+ const fileStats = await Promise.all(files.map((f) => stat(f).catch(() => null)));
81
+ const size = fileStats.reduce((sum, s) => sum + (s ? s.size : 0), 0);
82
+ const loc = await estimateLoc(files, size);
64
83
  return {
65
84
  packages,
66
85
  loc,
@@ -68,6 +87,17 @@ async function calculateStats(rootDir) {
68
87
  sizeFormatted: formatBytes(size)
69
88
  };
70
89
  }
90
+ async function calculateLinesOfCode(rootDir) {
91
+ const files = await getSourceFiles(rootDir);
92
+ const fileStats = await Promise.all(files.map((f) => stat(f).catch(() => null)));
93
+ const size = fileStats.reduce((sum, s) => sum + (s ? s.size : 0), 0);
94
+ return estimateLoc(files, size);
95
+ }
96
+ async function calculateSize(rootDir) {
97
+ const files = await getSourceFiles(rootDir);
98
+ const fileStats = await Promise.all(files.map((f) => stat(f).catch(() => null)));
99
+ return fileStats.reduce((sum, s) => sum + (s ? s.size : 0), 0);
100
+ }
71
101
 
72
102
  export { calculateLinesOfCode, calculateSize, calculateStats, countPackages, formatBytes };
73
103
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/stats/calculate-stats.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Calculate monorepo statistics\n *\n * Atomic functions for counting packages, LOC, size, etc.\n */\n\nimport { readFile, stat } from 'node:fs/promises';\nimport globby from 'globby';\n\nexport interface MonorepoStats {\n packages: number;\n loc: number;\n size: number;\n sizeFormatted: string;\n}\n\n/**\n * Calculate total lines of code in all source files\n */\nexport async function calculateLinesOfCode(rootDir: string, sourceFiles?: string[]): Promise<number> {\n const files = sourceFiles ?? await globby('**/*.{ts,tsx,js,jsx}', {\n cwd: rootDir,\n ignore: ['**/node_modules/**', '**/dist/**', '**/.git/**', '**/.kb/**', '**/build/**',\n '**/*.test.{ts,tsx,js,jsx}', '**/*.spec.{ts,tsx,js,jsx}'],\n absolute: true,\n deep: 8,\n });\n\n const contents = await Promise.all(files.map(f => readFile(f, 'utf-8').catch(() => null)));\n return contents.reduce((sum, c) => sum + (c ? c.split('\\n').length : 0), 0);\n}\n\n/**\n * Calculate total size of source files in bytes\n */\nexport async function calculateSize(rootDir: string, sourceFiles?: string[]): Promise<number> {\n const files = sourceFiles ?? await globby('**/*.{ts,tsx,js,jsx}', {\n cwd: rootDir,\n ignore: ['**/node_modules/**', '**/dist/**', '**/.git/**', '**/.kb/**', '**/build/**'],\n absolute: true,\n deep: 8,\n });\n\n const stats = await Promise.all(files.map(f => stat(f).catch(() => null)));\n return stats.reduce((sum, s) => sum + (s ? s.size : 0), 0);\n}\n\n/**\n * Format bytes to human-readable string\n */\nexport function formatBytes(bytes: number): string {\n if (bytes === 0) {return '0 B';}\n\n const units = ['B', 'KB', 'MB', 'GB'];\n const k = 1024;\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n const value = bytes / Math.pow(k, i);\n\n return `${value.toFixed(2)} ${units[i]}`;\n}\n\n/**\n * Count packages in monorepo\n */\nexport async function countPackages(rootDir: string): Promise<number> {\n const packageJsonFiles = await globby('**/package.json', {\n cwd: rootDir,\n ignore: ['**/node_modules/**', '**/.git/**', '**/.kb/**'],\n absolute: false,\n deep: 6,\n });\n\n // Exclude root package.json if it exists\n return packageJsonFiles.filter(p => p !== 'package.json').length;\n}\n\n/**\n * Calculate all stats at once\n */\nexport async function calculateStats(rootDir: string): Promise<MonorepoStats> {\n // Single scan for source files shared by LOC and size\n const sourceFiles = await globby('**/*.{ts,tsx,js,jsx}', {\n cwd: rootDir,\n ignore: ['**/node_modules/**', '**/dist/**', '**/.git/**', '**/.kb/**', '**/build/**'],\n absolute: true,\n deep: 8,\n });\n\n const [packages, loc, size] = await Promise.all([\n countPackages(rootDir),\n calculateLinesOfCode(rootDir, sourceFiles),\n calculateSize(rootDir, sourceFiles),\n ]);\n\n return {\n packages,\n loc,\n size,\n sizeFormatted: formatBytes(size),\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/stats/calculate-stats.ts"],"names":["fsReadFile"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Calculate monorepo statistics\n *\n * Fast path: git ls-files (index read, no FS traversal) + stat (metadata only) +\n * sampling 200 files to calibrate bytes-per-line ratio for the LOC estimate.\n */\n\nimport { readFile as fsReadFile, stat } from 'node:fs/promises';\nimport { execFile } from 'node:child_process';\nimport path from 'node:path';\nimport globby from 'globby';\n\nconst LOC_SAMPLE_SIZE = 200;\nconst FALLBACK_BYTES_PER_LINE = 45;\n\nasync function readTextFile(f: string): Promise<string | null> {\n try {\n return await fsReadFile(f, 'utf8');\n } catch {\n return null;\n }\n}\n\nexport interface MonorepoStats {\n packages: number;\n loc: number;\n size: number;\n sizeFormatted: string;\n}\n\nconst SOURCE_PATTERNS = ['*.ts', '*.tsx', '*.js', '*.jsx', '*.go'];\n\nasync function getSourceFiles(rootDir: string): Promise<string[]> {\n return new Promise((resolve) => {\n execFile(\n 'git',\n ['ls-files', '-z', '--cached', '--others', '--exclude-standard', '--', ...SOURCE_PATTERNS],\n { cwd: rootDir, maxBuffer: 64 * 1024 * 1024 },\n (err, stdout) => {\n if (err || !stdout) { return resolve([]); }\n resolve(\n stdout\n .split('\\0')\n .filter(Boolean)\n .map(f => path.join(rootDir, f))\n );\n }\n );\n });\n}\n\nasync function estimateLoc(files: string[], totalSize: number): Promise<number> {\n if (files.length === 0) { return 0; }\n\n const indices = new Set<number>();\n while (indices.size < Math.min(LOC_SAMPLE_SIZE, files.length)) {\n indices.add(Math.floor(Math.random() * files.length));\n }\n const sample = Array.from(indices).map(i => files[i]).filter((f): f is string => f !== undefined);\n\n const contents = await Promise.all(sample.map(readTextFile));\n\n let sampleBytes = 0;\n let sampleLines = 0;\n for (const c of contents) {\n if (!c) { continue; }\n sampleBytes += Buffer.byteLength(c, 'utf-8');\n sampleLines += c.split('\\n').length;\n }\n\n const bytesPerLine = sampleLines > 0 ? sampleBytes / sampleLines : FALLBACK_BYTES_PER_LINE;\n return Math.round(totalSize / bytesPerLine);\n}\n\n/**\n * Format bytes to human-readable string\n */\nexport function formatBytes(bytes: number): string {\n if (bytes === 0) { return '0 B'; }\n\n const units = ['B', 'KB', 'MB', 'GB'];\n const k = 1024;\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n const value = bytes / Math.pow(k, i);\n\n return `${value.toFixed(2)} ${units[i]}`;\n}\n\n/**\n * Count packages in monorepo\n */\nexport async function countPackages(rootDir: string): Promise<number> {\n const packageJsonFiles = await globby('**/package.json', {\n cwd: rootDir,\n ignore: ['**/node_modules/**', '**/.git/**', '**/.kb/**'],\n absolute: false,\n deep: 6,\n });\n\n return packageJsonFiles.filter(p => p !== 'package.json').length;\n}\n\n/**\n * Calculate all stats at once\n */\nexport async function calculateStats(rootDir: string): Promise<MonorepoStats> {\n const [files, packages] = await Promise.all([\n getSourceFiles(rootDir),\n countPackages(rootDir),\n ]);\n\n const fileStats = await Promise.all(files.map(f => stat(f).catch(() => null)));\n const size = fileStats.reduce((sum, s) => sum + (s ? s.size : 0), 0);\n\n const loc = await estimateLoc(files, size);\n\n return {\n packages,\n loc,\n size,\n sizeFormatted: formatBytes(size),\n };\n}\n\n// Keep named exports for external callers\nexport async function calculateLinesOfCode(rootDir: string): Promise<number> {\n const files = await getSourceFiles(rootDir);\n const fileStats = await Promise.all(files.map(f => stat(f).catch(() => null)));\n const size = fileStats.reduce((sum, s) => sum + (s ? s.size : 0), 0);\n return estimateLoc(files, size);\n}\n\nexport async function calculateSize(rootDir: string): Promise<number> {\n const files = await getSourceFiles(rootDir);\n const fileStats = await Promise.all(files.map(f => stat(f).catch(() => null)));\n return fileStats.reduce((sum, s) => sum + (s ? s.size : 0), 0);\n}\n"]}
@@ -1 +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: unknown) {\n const spawnErr = err as { code?: number; message?: string; stdout?: string; stderr?: string };\n return {\n success: false,\n exitCode: spawnErr.code || 1,\n error: spawnErr.message || 'Test execution failed',\n output: (spawnErr.stdout || '') + (spawnErr.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"]}
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: unknown) {\n const spawnErr = err as { code?: number; message?: string; stdout?: string; stderr?: string };\n return {\n success: false,\n exitCode: spawnErr.code || 1,\n error: spawnErr.message || 'Test execution failed',\n output: (spawnErr.stdout || '') + (spawnErr.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"]}
@@ -58,7 +58,7 @@ function createProgram(packageDir, tsconfigPath) {
58
58
  rootNames: parsedConfig.fileNames,
59
59
  options: parsedConfig.options
60
60
  });
61
- } catch (err) {
61
+ } catch {
62
62
  return null;
63
63
  }
64
64
  }
@@ -1 +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"]}
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 {\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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kb-labs/quality-core",
3
3
  "description": "Core business logic for KB Labs Quality plugin - monorepo analysis, dependency management, and health checks.",
4
- "version": "2.93.0",
4
+ "version": "2.96.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -57,15 +57,15 @@
57
57
  "globby": "^11.0.0",
58
58
  "minimatch": "^10.0.1",
59
59
  "typescript": "^5.6.3",
60
- "@kb-labs/quality-contracts": "2.93.0",
61
- "@kb-labs/sdk": "2.21.2"
60
+ "@kb-labs/sdk": "2.21.2",
61
+ "@kb-labs/quality-contracts": "2.96.0"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@types/node": "^24.3.3",
65
65
  "rimraf": "^6.0.1",
66
66
  "tsup": "^8.5.0",
67
- "vitest": "^3.2.4",
68
- "@kb-labs/devkit": "2.93.0"
67
+ "vitest": "^3.2.6",
68
+ "@kb-labs/devkit": "2.96.0"
69
69
  },
70
70
  "engines": {
71
71
  "node": ">=20.0.0",