@kb-labs/quality-core 2.119.0-canary.e120bc0ee → 2.119.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builds/index.js.map +1 -1
- package/dist/dead-code/index.js.map +1 -1
- package/dist/dependencies/index.js.map +1 -1
- package/dist/graph/index.js.map +1 -1
- package/dist/health/index.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/stale/index.js.map +1 -1
- package/dist/stats/index.js.map +1 -1
- package/dist/tests/index.js.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +4 -4
package/dist/health/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/health/calculate-health.ts"],"names":[],"mappings":";;;AAuBA,SAAS,eAAe,KAAA,EAAqC;AAC3D,EAAA,KAAA,MAAW,CAAC,GAAG,EAAE,GAAA,EAAK,CAAA,IAAK,MAAA,CAAO,OAAA,CAAQ,aAAa,CAAA,EAAgD;AACrG,IAAA,IAAI,SAAS,GAAA,EAAK;AAAC,MAAA,OAAO,CAAA;AAAA,IAAE;AAAA,EAC9B;AACA,EAAA,OAAO,GAAA;AACT;AAEA,SAAS,MAAM,CAAA,EAAmB;AAChC,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,KAAK,IAAA,CAAK,KAAA,CAAM,CAAC,CAAC,CAAC,CAAA;AACjD;AAIA,SAAS,iBAAA,CACP,QAAA,EACA,QAAA,EACA,UAAA,EACgB;AAChB,EAAA,MAAM,UAAoB,EAAC;AAG3B,EAAA,MAAM,kBAAkB,IAAA,CAAK,GAAA,CAAI,QAAA,CAAS,eAAA,GAAkB,GAAG,EAAE,CAAA;AACjE,EAAA,IAAI,QAAA,CAAS,kBAAkB,CAAA,EAAG;AAChC,IAAA,OAAA,CAAQ,IAAA,CAAK,GAAG,QAAA,CAAS,eAAe,6BAA6B,QAAA,CAAS,gBAAA,CAAiB,MAAM,CAAA,WAAA,CAAa,CAAA;AAAA,EACpH;AAGA,EAAA,MAAM,eAAA,GAAkB,SAAS,QAAA,CAAS,MAAA,CAAO,OAAK,CAAA,CAAE,WAAA,GAAc,WAAW,WAAW,CAAA;AAC5F,EAAA,MAAM,kBAAkB,IAAA,CAAK,GAAA,CAAI,eAAA,CAAgB,MAAA,GAAS,GAAG,EAAE,CAAA;AAC/D,EAAA,IAAI,eAAA,CAAgB,SAAS,CAAA,EAAG;AAC9B,IAAA,OAAA,CAAQ,KAAK,CAAA,EAAG,eAAA,CAAgB,MAAM,CAAA,+BAAA,EAAkC,UAAA,CAAW,WAAW,CAAA,CAAE,CAAA;AAAA,EAClG;AAEA,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,GAAA,GAAM,eAAA,GAAkB,eAAe,CAAA;AAC3D,EAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,cAAA,CAAe,KAAK,GAAG,OAAA,EAAQ;AACxD;AAEA,SAAS,gBAAgB,KAAA,EAA2C;AAClE,EAAA,MAAM,UAAoB,EAAC;AAE3B,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,QAAA,CAAS,MAAA,CAAO,CAAC,GAAG,CAAA,KAAM,CAAA,GAAI,CAAA,CAAE,QAAA,EAAU,CAAC,CAAA;AAClE,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,GAAA,CAAI,QAAA,GAAW,KAAK,EAAE,CAAA;AAC9C,EAAA,IAAI,WAAW,CAAA,EAAG;AAAC,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,EAAG,QAAQ,CAAA,iBAAA,CAAmB,CAAA;AAAA,EAAE;AAEhE,EAAA,MAAM,WAAA,GAAc,KAAA,CAAM,QAAA,CAAS,MAAA,CAAO,CAAC,GAAG,CAAA,KAAM,CAAA,GAAI,CAAA,CAAE,aAAA,EAAe,CAAC,CAAA;AAC1E,EAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,GAAA,CAAI,WAAA,GAAc,GAAG,EAAE,CAAA;AAClD,EAAA,IAAI,cAAc,CAAA,EAAG;AAAC,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,EAAG,WAAW,CAAA,cAAA,CAAgB,CAAA;AAAA,EAAE;AAEnE,EAAA,MAAM,eAAe,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,WAAA,GAAc,GAAG,EAAE,CAAA;AACvD,EAAA,IAAI,KAAA,CAAM,cAAc,CAAA,EAAG;AAAC,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,EAAG,KAAA,CAAM,WAAW,CAAA,cAAA,CAAgB,CAAA;AAAA,EAAE;AAE/E,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,GAAA,GAAM,UAAA,GAAa,gBAAgB,YAAY,CAAA;AACnE,EAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,cAAA,CAAe,KAAK,GAAG,OAAA,EAAQ;AACxD;AAEA,SAAS,cAAc,IAAA,EAAkC;AACvD,EAAA,MAAM,UAAoB,EAAC;AAE3B,EAAA,MAAM,cAAc,IAAA,CAAK,GAAA,CAAI,KAAK,WAAA,CAAY,MAAA,GAAS,GAAG,EAAE,CAAA;AAC5D,EAAA,IAAI,IAAA,CAAK,WAAA,CAAY,MAAA,GAAS,CAAA,EAAG;AAAC,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,EAAG,IAAA,CAAK,WAAA,CAAY,MAAM,CAAA,eAAA,CAAiB,CAAA;AAAA,EAAE;AAE5F,EAAA,MAAM,gBAAgB,IAAA,CAAK,GAAA,CAAI,KAAK,aAAA,CAAc,MAAA,GAAS,KAAK,EAAE,CAAA;AAClE,EAAA,IAAI,IAAA,CAAK,aAAA,CAAc,MAAA,GAAS,CAAA,EAAG;AAAC,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,EAAG,IAAA,CAAK,aAAA,CAAc,MAAM,CAAA,iBAAA,CAAmB,CAAA;AAAA,EAAE;AAElG,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,GAAA,GAAM,WAAA,GAAc,aAAa,CAAA;AACrD,EAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,cAAA,CAAe,KAAK,GAAG,OAAA,EAAQ;AACxD;AAEA,SAAS,gBAAgB,IAAA,EAAkC;AACzD,EAAA,MAAM,UAAoB,EAAC;AAE3B,EAAA,MAAM,gBAAgB,IAAA,CAAK,GAAA,CAAI,KAAK,kBAAA,CAAmB,MAAA,GAAS,GAAG,EAAE,CAAA;AACrE,EAAA,IAAI,IAAA,CAAK,kBAAA,CAAmB,MAAA,GAAS,CAAA,EAAG;AAAC,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,EAAG,IAAA,CAAK,kBAAA,CAAmB,MAAM,CAAA,cAAA,CAAgB,CAAA;AAAA,EAAE;AAEzG,EAAA,MAAM,kBAAkB,IAAA,CAAK,GAAA,CAAI,KAAK,oBAAA,CAAqB,MAAA,GAAS,IAAI,EAAE,CAAA;AAC1E,EAAA,IAAI,IAAA,CAAK,oBAAA,CAAqB,MAAA,GAAS,CAAA,EAAG;AAAC,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,EAAG,IAAA,CAAK,oBAAA,CAAqB,MAAM,CAAA,gBAAA,CAAkB,CAAA;AAAA,EAAE;AAE/G,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,GAAA,GAAM,aAAA,GAAgB,eAAe,CAAA;AACzD,EAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,cAAA,CAAe,KAAK,GAAG,OAAA,EAAQ;AACxD;AAEA,SAAS,kBAAkB,WAAA,EAA4C;AACrE,EAAA,IAAI,gBAAgB,IAAA,EAAM;AACxB,IAAA,OAAO,EAAE,OAAO,GAAA,EAAK,KAAA,EAAO,KAAK,OAAA,EAAS,CAAC,iCAA4B,CAAA,EAAE;AAAA,EAC3E;AACA,EAAA,MAAM,KAAA,GAAQ,MAAM,WAAW,CAAA;AAC/B,EAAA,MAAM,OAAA,GAAU,WAAA,GAAc,EAAA,GAAK,CAAC,CAAA,aAAA,EAAgB,WAAA,CAAY,OAAA,CAAQ,CAAC,CAAC,CAAA,CAAA,CAAG,CAAA,GAAI,EAAC;AAClF,EAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,cAAA,CAAe,KAAK,GAAG,OAAA,EAAQ;AACxD;AAeO,SAAS,gBAAgB,KAAA,EAAiC;AAC/D,EAAA,MAAM,EAAE,QAAA,EAAU,QAAA,EAAU,OAAO,IAAA,EAAM,eAAA,EAAiB,YAAW,GAAI,KAAA;AAEzE,EAAA,MAAM,UAAA,GAA8B;AAAA,IAClC,YAAA,EAAc,iBAAA,CAAkB,QAAA,EAAU,QAAA,EAAU,UAAU,CAAA;AAAA,IAC9D,UAAA,EAAY,gBAAgB,KAAK,CAAA;AAAA,IACjC,QAAA,EAAU,cAAc,IAAI,CAAA;AAAA,IAC5B,UAAA,EAAY,gBAAgB,IAAI,CAAA;AAAA,IAChC,YAAA,EAAc,kBAAkB,eAAe;AAAA,GACjD;AAEA,EAAA,MAAM,KAAA,GAAQ,KAAA;AAAA,IACZ,MAAA,CAAO,OAAA,CAAQ,iBAAiB,CAAA,CAAE,MAAA;AAAA,MAChC,CAAC,GAAA,EAAK,CAAC,GAAA,EAAK,MAAM,MAAM,GAAA,GAAM,UAAA,CAAW,GAA4B,CAAA,CAAE,KAAA,GAAQ,MAAA;AAAA,MAC/E;AAAA;AACF,GACF;AAEA,EAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,cAAA,CAAe,KAAK,GAAG,UAAA,EAAW;AAC3D","file":"index.js","sourcesContent":["/**\n * Multidimensional health score calculation.\n *\n * Composite score = weighted average of 5 dimensions:\n * architecture 30% — layering violations + avg instability\n * typescript 25% — any count + ts-ignore count\n * deadCode 20% — unused files + unused exports from knip\n * depHygiene 15% — unused + unlisted dependencies from knip\n * testCoverage 10% — avg test coverage % (optional, defaults to 100 if unavailable)\n */\n\nimport { DIMENSION_WEIGHTS, HEALTH_GRADES } from '@kb-labs/quality-contracts';\nimport type {\n HealthScore,\n DimensionScore,\n DimensionScores,\n LayeringReport,\n CouplingReport,\n KnipReport,\n TypeAnalysisResult,\n QualityThresholds,\n} from '@kb-labs/quality-contracts';\n\nfunction gradeFromScore(score: number): HealthScore['grade'] {\n for (const [g, { min }] of Object.entries(HEALTH_GRADES) as [HealthScore['grade'], { min: number }][]) {\n if (score >= min) {return g;}\n }\n return 'F';\n}\n\nfunction clamp(n: number): number {\n return Math.max(0, Math.min(100, Math.round(n)));\n}\n\n// ── Dimension scorers ─────────────────────────────────────────────────────────\n\nfunction scoreArchitecture(\n layering: LayeringReport,\n coupling: CouplingReport,\n thresholds: QualityThresholds\n): DimensionScore {\n const details: string[] = [];\n\n // Each violation costs 5 points, max -60\n const layeringPenalty = Math.min(layering.totalViolations * 5, 60);\n if (layering.totalViolations > 0) {\n details.push(`${layering.totalViolations} layering violation(s) in ${layering.affectedPackages.length} package(s)`);\n }\n\n // Packages above instability threshold cost 3 points each, max -30\n const highInstability = coupling.packages.filter(p => p.instability > thresholds.instability);\n const couplingPenalty = Math.min(highInstability.length * 3, 30);\n if (highInstability.length > 0) {\n details.push(`${highInstability.length} package(s) with instability > ${thresholds.instability}`);\n }\n\n const score = clamp(100 - layeringPenalty - couplingPenalty);\n return { score, grade: gradeFromScore(score), details };\n}\n\nfunction scoreTypeScript(types: TypeAnalysisResult): DimensionScore {\n const details: string[] = [];\n\n const totalAny = types.packages.reduce((s, p) => s + p.anyCount, 0);\n const anyPenalty = Math.min(totalAny * 0.5, 50);\n if (totalAny > 0) {details.push(`${totalAny} \\`any\\` usage(s)`);}\n\n const totalIgnore = types.packages.reduce((s, p) => s + p.tsIgnoreCount, 0);\n const ignorePenalty = Math.min(totalIgnore * 2, 30);\n if (totalIgnore > 0) {details.push(`${totalIgnore} @ts-ignore(s)`);}\n\n const errorPenalty = Math.min(types.totalErrors * 3, 20);\n if (types.totalErrors > 0) {details.push(`${types.totalErrors} type error(s)`);}\n\n const score = clamp(100 - anyPenalty - ignorePenalty - errorPenalty);\n return { score, grade: gradeFromScore(score), details };\n}\n\nfunction scoreDeadCode(knip: KnipReport): DimensionScore {\n const details: string[] = [];\n\n const filePenalty = Math.min(knip.unusedFiles.length * 2, 50);\n if (knip.unusedFiles.length > 0) {details.push(`${knip.unusedFiles.length} unused file(s)`);}\n\n const exportPenalty = Math.min(knip.unusedExports.length * 0.5, 30);\n if (knip.unusedExports.length > 0) {details.push(`${knip.unusedExports.length} unused export(s)`);}\n\n const score = clamp(100 - filePenalty - exportPenalty);\n return { score, grade: gradeFromScore(score), details };\n}\n\nfunction scoreDepHygiene(knip: KnipReport): DimensionScore {\n const details: string[] = [];\n\n const unusedPenalty = Math.min(knip.unusedDependencies.length * 5, 50);\n if (knip.unusedDependencies.length > 0) {details.push(`${knip.unusedDependencies.length} unused dep(s)`);}\n\n const unlistedPenalty = Math.min(knip.unlistedDependencies.length * 10, 40);\n if (knip.unlistedDependencies.length > 0) {details.push(`${knip.unlistedDependencies.length} unlisted dep(s)`);}\n\n const score = clamp(100 - unusedPenalty - unlistedPenalty);\n return { score, grade: gradeFromScore(score), details };\n}\n\nfunction scoreTestCoverage(avgCoverage: number | null): DimensionScore {\n if (avgCoverage === null) {\n return { score: 100, grade: 'A', details: ['no coverage data — skipped'] };\n }\n const score = clamp(avgCoverage);\n const details = avgCoverage < 80 ? [`avg coverage ${avgCoverage.toFixed(1)}%`] : [];\n return { score, grade: gradeFromScore(score), details };\n}\n\n// ── Public API ────────────────────────────────────────────────────────────────\n\nexport interface HealthInput {\n layering: LayeringReport;\n coupling: CouplingReport;\n types: TypeAnalysisResult;\n knip: KnipReport;\n totalPackages: number;\n /** Average test coverage in %, or null if not collected */\n avgTestCoverage: number | null;\n thresholds: QualityThresholds;\n}\n\nexport function calculateHealth(input: HealthInput): HealthScore {\n const { layering, coupling, types, knip, avgTestCoverage, thresholds } = input;\n\n const dimensions: DimensionScores = {\n architecture: scoreArchitecture(layering, coupling, thresholds),\n typescript: scoreTypeScript(types),\n deadCode: scoreDeadCode(knip),\n depHygiene: scoreDepHygiene(knip),\n testCoverage: scoreTestCoverage(avgTestCoverage),\n };\n\n const score = clamp(\n Object.entries(DIMENSION_WEIGHTS).reduce(\n (sum, [key, weight]) => sum + dimensions[key as keyof DimensionScores].score * weight,\n 0\n )\n );\n\n return { score, grade: gradeFromScore(score), dimensions };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../home/runner/work/kb-labs/kb-labs/plugins/quality/core/src/health/calculate-health.ts"],"names":[],"mappings":";;;AAuBA,SAAS,eAAe,KAAA,EAAqC;AAC3D,EAAA,KAAA,MAAW,CAAC,GAAG,EAAE,GAAA,EAAK,CAAA,IAAK,MAAA,CAAO,OAAA,CAAQ,aAAa,CAAA,EAAgD;AACrG,IAAA,IAAI,SAAS,GAAA,EAAK;AAAC,MAAA,OAAO,CAAA;AAAA,IAAE;AAAA,EAC9B;AACA,EAAA,OAAO,GAAA;AACT;AAEA,SAAS,MAAM,CAAA,EAAmB;AAChC,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,KAAK,IAAA,CAAK,KAAA,CAAM,CAAC,CAAC,CAAC,CAAA;AACjD;AAIA,SAAS,iBAAA,CACP,QAAA,EACA,QAAA,EACA,UAAA,EACgB;AAChB,EAAA,MAAM,UAAoB,EAAC;AAG3B,EAAA,MAAM,kBAAkB,IAAA,CAAK,GAAA,CAAI,QAAA,CAAS,eAAA,GAAkB,GAAG,EAAE,CAAA;AACjE,EAAA,IAAI,QAAA,CAAS,kBAAkB,CAAA,EAAG;AAChC,IAAA,OAAA,CAAQ,IAAA,CAAK,GAAG,QAAA,CAAS,eAAe,6BAA6B,QAAA,CAAS,gBAAA,CAAiB,MAAM,CAAA,WAAA,CAAa,CAAA;AAAA,EACpH;AAGA,EAAA,MAAM,eAAA,GAAkB,SAAS,QAAA,CAAS,MAAA,CAAO,OAAK,CAAA,CAAE,WAAA,GAAc,WAAW,WAAW,CAAA;AAC5F,EAAA,MAAM,kBAAkB,IAAA,CAAK,GAAA,CAAI,eAAA,CAAgB,MAAA,GAAS,GAAG,EAAE,CAAA;AAC/D,EAAA,IAAI,eAAA,CAAgB,SAAS,CAAA,EAAG;AAC9B,IAAA,OAAA,CAAQ,KAAK,CAAA,EAAG,eAAA,CAAgB,MAAM,CAAA,+BAAA,EAAkC,UAAA,CAAW,WAAW,CAAA,CAAE,CAAA;AAAA,EAClG;AAEA,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,GAAA,GAAM,eAAA,GAAkB,eAAe,CAAA;AAC3D,EAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,cAAA,CAAe,KAAK,GAAG,OAAA,EAAQ;AACxD;AAEA,SAAS,gBAAgB,KAAA,EAA2C;AAClE,EAAA,MAAM,UAAoB,EAAC;AAE3B,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,QAAA,CAAS,MAAA,CAAO,CAAC,GAAG,CAAA,KAAM,CAAA,GAAI,CAAA,CAAE,QAAA,EAAU,CAAC,CAAA;AAClE,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,GAAA,CAAI,QAAA,GAAW,KAAK,EAAE,CAAA;AAC9C,EAAA,IAAI,WAAW,CAAA,EAAG;AAAC,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,EAAG,QAAQ,CAAA,iBAAA,CAAmB,CAAA;AAAA,EAAE;AAEhE,EAAA,MAAM,WAAA,GAAc,KAAA,CAAM,QAAA,CAAS,MAAA,CAAO,CAAC,GAAG,CAAA,KAAM,CAAA,GAAI,CAAA,CAAE,aAAA,EAAe,CAAC,CAAA;AAC1E,EAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,GAAA,CAAI,WAAA,GAAc,GAAG,EAAE,CAAA;AAClD,EAAA,IAAI,cAAc,CAAA,EAAG;AAAC,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,EAAG,WAAW,CAAA,cAAA,CAAgB,CAAA;AAAA,EAAE;AAEnE,EAAA,MAAM,eAAe,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,WAAA,GAAc,GAAG,EAAE,CAAA;AACvD,EAAA,IAAI,KAAA,CAAM,cAAc,CAAA,EAAG;AAAC,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,EAAG,KAAA,CAAM,WAAW,CAAA,cAAA,CAAgB,CAAA;AAAA,EAAE;AAE/E,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,GAAA,GAAM,UAAA,GAAa,gBAAgB,YAAY,CAAA;AACnE,EAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,cAAA,CAAe,KAAK,GAAG,OAAA,EAAQ;AACxD;AAEA,SAAS,cAAc,IAAA,EAAkC;AACvD,EAAA,MAAM,UAAoB,EAAC;AAE3B,EAAA,MAAM,cAAc,IAAA,CAAK,GAAA,CAAI,KAAK,WAAA,CAAY,MAAA,GAAS,GAAG,EAAE,CAAA;AAC5D,EAAA,IAAI,IAAA,CAAK,WAAA,CAAY,MAAA,GAAS,CAAA,EAAG;AAAC,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,EAAG,IAAA,CAAK,WAAA,CAAY,MAAM,CAAA,eAAA,CAAiB,CAAA;AAAA,EAAE;AAE5F,EAAA,MAAM,gBAAgB,IAAA,CAAK,GAAA,CAAI,KAAK,aAAA,CAAc,MAAA,GAAS,KAAK,EAAE,CAAA;AAClE,EAAA,IAAI,IAAA,CAAK,aAAA,CAAc,MAAA,GAAS,CAAA,EAAG;AAAC,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,EAAG,IAAA,CAAK,aAAA,CAAc,MAAM,CAAA,iBAAA,CAAmB,CAAA;AAAA,EAAE;AAElG,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,GAAA,GAAM,WAAA,GAAc,aAAa,CAAA;AACrD,EAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,cAAA,CAAe,KAAK,GAAG,OAAA,EAAQ;AACxD;AAEA,SAAS,gBAAgB,IAAA,EAAkC;AACzD,EAAA,MAAM,UAAoB,EAAC;AAE3B,EAAA,MAAM,gBAAgB,IAAA,CAAK,GAAA,CAAI,KAAK,kBAAA,CAAmB,MAAA,GAAS,GAAG,EAAE,CAAA;AACrE,EAAA,IAAI,IAAA,CAAK,kBAAA,CAAmB,MAAA,GAAS,CAAA,EAAG;AAAC,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,EAAG,IAAA,CAAK,kBAAA,CAAmB,MAAM,CAAA,cAAA,CAAgB,CAAA;AAAA,EAAE;AAEzG,EAAA,MAAM,kBAAkB,IAAA,CAAK,GAAA,CAAI,KAAK,oBAAA,CAAqB,MAAA,GAAS,IAAI,EAAE,CAAA;AAC1E,EAAA,IAAI,IAAA,CAAK,oBAAA,CAAqB,MAAA,GAAS,CAAA,EAAG;AAAC,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,EAAG,IAAA,CAAK,oBAAA,CAAqB,MAAM,CAAA,gBAAA,CAAkB,CAAA;AAAA,EAAE;AAE/G,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,GAAA,GAAM,aAAA,GAAgB,eAAe,CAAA;AACzD,EAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,cAAA,CAAe,KAAK,GAAG,OAAA,EAAQ;AACxD;AAEA,SAAS,kBAAkB,WAAA,EAA4C;AACrE,EAAA,IAAI,gBAAgB,IAAA,EAAM;AACxB,IAAA,OAAO,EAAE,OAAO,GAAA,EAAK,KAAA,EAAO,KAAK,OAAA,EAAS,CAAC,iCAA4B,CAAA,EAAE;AAAA,EAC3E;AACA,EAAA,MAAM,KAAA,GAAQ,MAAM,WAAW,CAAA;AAC/B,EAAA,MAAM,OAAA,GAAU,WAAA,GAAc,EAAA,GAAK,CAAC,CAAA,aAAA,EAAgB,WAAA,CAAY,OAAA,CAAQ,CAAC,CAAC,CAAA,CAAA,CAAG,CAAA,GAAI,EAAC;AAClF,EAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,cAAA,CAAe,KAAK,GAAG,OAAA,EAAQ;AACxD;AAeO,SAAS,gBAAgB,KAAA,EAAiC;AAC/D,EAAA,MAAM,EAAE,QAAA,EAAU,QAAA,EAAU,OAAO,IAAA,EAAM,eAAA,EAAiB,YAAW,GAAI,KAAA;AAEzE,EAAA,MAAM,UAAA,GAA8B;AAAA,IAClC,YAAA,EAAc,iBAAA,CAAkB,QAAA,EAAU,QAAA,EAAU,UAAU,CAAA;AAAA,IAC9D,UAAA,EAAY,gBAAgB,KAAK,CAAA;AAAA,IACjC,QAAA,EAAU,cAAc,IAAI,CAAA;AAAA,IAC5B,UAAA,EAAY,gBAAgB,IAAI,CAAA;AAAA,IAChC,YAAA,EAAc,kBAAkB,eAAe;AAAA,GACjD;AAEA,EAAA,MAAM,KAAA,GAAQ,KAAA;AAAA,IACZ,MAAA,CAAO,OAAA,CAAQ,iBAAiB,CAAA,CAAE,MAAA;AAAA,MAChC,CAAC,GAAA,EAAK,CAAC,GAAA,EAAK,MAAM,MAAM,GAAA,GAAM,UAAA,CAAW,GAA4B,CAAA,CAAE,KAAA,GAAQ,MAAA;AAAA,MAC/E;AAAA;AACF,GACF;AAEA,EAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,cAAA,CAAe,KAAK,GAAG,UAAA,EAAW;AAC3D","file":"index.js","sourcesContent":["/**\n * Multidimensional health score calculation.\n *\n * Composite score = weighted average of 5 dimensions:\n * architecture 30% — layering violations + avg instability\n * typescript 25% — any count + ts-ignore count\n * deadCode 20% — unused files + unused exports from knip\n * depHygiene 15% — unused + unlisted dependencies from knip\n * testCoverage 10% — avg test coverage % (optional, defaults to 100 if unavailable)\n */\n\nimport { DIMENSION_WEIGHTS, HEALTH_GRADES } from '@kb-labs/quality-contracts';\nimport type {\n HealthScore,\n DimensionScore,\n DimensionScores,\n LayeringReport,\n CouplingReport,\n KnipReport,\n TypeAnalysisResult,\n QualityThresholds,\n} from '@kb-labs/quality-contracts';\n\nfunction gradeFromScore(score: number): HealthScore['grade'] {\n for (const [g, { min }] of Object.entries(HEALTH_GRADES) as [HealthScore['grade'], { min: number }][]) {\n if (score >= min) {return g;}\n }\n return 'F';\n}\n\nfunction clamp(n: number): number {\n return Math.max(0, Math.min(100, Math.round(n)));\n}\n\n// ── Dimension scorers ─────────────────────────────────────────────────────────\n\nfunction scoreArchitecture(\n layering: LayeringReport,\n coupling: CouplingReport,\n thresholds: QualityThresholds\n): DimensionScore {\n const details: string[] = [];\n\n // Each violation costs 5 points, max -60\n const layeringPenalty = Math.min(layering.totalViolations * 5, 60);\n if (layering.totalViolations > 0) {\n details.push(`${layering.totalViolations} layering violation(s) in ${layering.affectedPackages.length} package(s)`);\n }\n\n // Packages above instability threshold cost 3 points each, max -30\n const highInstability = coupling.packages.filter(p => p.instability > thresholds.instability);\n const couplingPenalty = Math.min(highInstability.length * 3, 30);\n if (highInstability.length > 0) {\n details.push(`${highInstability.length} package(s) with instability > ${thresholds.instability}`);\n }\n\n const score = clamp(100 - layeringPenalty - couplingPenalty);\n return { score, grade: gradeFromScore(score), details };\n}\n\nfunction scoreTypeScript(types: TypeAnalysisResult): DimensionScore {\n const details: string[] = [];\n\n const totalAny = types.packages.reduce((s, p) => s + p.anyCount, 0);\n const anyPenalty = Math.min(totalAny * 0.5, 50);\n if (totalAny > 0) {details.push(`${totalAny} \\`any\\` usage(s)`);}\n\n const totalIgnore = types.packages.reduce((s, p) => s + p.tsIgnoreCount, 0);\n const ignorePenalty = Math.min(totalIgnore * 2, 30);\n if (totalIgnore > 0) {details.push(`${totalIgnore} @ts-ignore(s)`);}\n\n const errorPenalty = Math.min(types.totalErrors * 3, 20);\n if (types.totalErrors > 0) {details.push(`${types.totalErrors} type error(s)`);}\n\n const score = clamp(100 - anyPenalty - ignorePenalty - errorPenalty);\n return { score, grade: gradeFromScore(score), details };\n}\n\nfunction scoreDeadCode(knip: KnipReport): DimensionScore {\n const details: string[] = [];\n\n const filePenalty = Math.min(knip.unusedFiles.length * 2, 50);\n if (knip.unusedFiles.length > 0) {details.push(`${knip.unusedFiles.length} unused file(s)`);}\n\n const exportPenalty = Math.min(knip.unusedExports.length * 0.5, 30);\n if (knip.unusedExports.length > 0) {details.push(`${knip.unusedExports.length} unused export(s)`);}\n\n const score = clamp(100 - filePenalty - exportPenalty);\n return { score, grade: gradeFromScore(score), details };\n}\n\nfunction scoreDepHygiene(knip: KnipReport): DimensionScore {\n const details: string[] = [];\n\n const unusedPenalty = Math.min(knip.unusedDependencies.length * 5, 50);\n if (knip.unusedDependencies.length > 0) {details.push(`${knip.unusedDependencies.length} unused dep(s)`);}\n\n const unlistedPenalty = Math.min(knip.unlistedDependencies.length * 10, 40);\n if (knip.unlistedDependencies.length > 0) {details.push(`${knip.unlistedDependencies.length} unlisted dep(s)`);}\n\n const score = clamp(100 - unusedPenalty - unlistedPenalty);\n return { score, grade: gradeFromScore(score), details };\n}\n\nfunction scoreTestCoverage(avgCoverage: number | null): DimensionScore {\n if (avgCoverage === null) {\n return { score: 100, grade: 'A', details: ['no coverage data — skipped'] };\n }\n const score = clamp(avgCoverage);\n const details = avgCoverage < 80 ? [`avg coverage ${avgCoverage.toFixed(1)}%`] : [];\n return { score, grade: gradeFromScore(score), details };\n}\n\n// ── Public API ────────────────────────────────────────────────────────────────\n\nexport interface HealthInput {\n layering: LayeringReport;\n coupling: CouplingReport;\n types: TypeAnalysisResult;\n knip: KnipReport;\n totalPackages: number;\n /** Average test coverage in %, or null if not collected */\n avgTestCoverage: number | null;\n thresholds: QualityThresholds;\n}\n\nexport function calculateHealth(input: HealthInput): HealthScore {\n const { layering, coupling, types, knip, avgTestCoverage, thresholds } = input;\n\n const dimensions: DimensionScores = {\n architecture: scoreArchitecture(layering, coupling, thresholds),\n typescript: scoreTypeScript(types),\n deadCode: scoreDeadCode(knip),\n depHygiene: scoreDepHygiene(knip),\n testCoverage: scoreTestCoverage(avgTestCoverage),\n };\n\n const score = clamp(\n Object.entries(DIMENSION_WEIGHTS).reduce(\n (sum, [key, weight]) => sum + dimensions[key as keyof DimensionScores].score * weight,\n 0\n )\n );\n\n return { score, grade: gradeFromScore(score), dimensions };\n}\n"]}
|