Description
calculateCouplingScore in src/utils/complexityCalculator.ts divides by modules.length without guarding against an empty modules array. When a snapshot has zero modules, this produces 0 / 0 = NaN, which propagates through calculateRepositoryHealth and makes the repository health score NaN.
Every sibling scoring function in the same file guards the empty case — calculateModularityScore (if (modules.length === 0) return 0) and calculateCohesionScore (if (modules.length === 0) return 100) — but calculateCouplingScore does not.
Location
src/utils/complexityCalculator.ts, calculateCouplingScore (lines ~46–54):
export const calculateCouplingScore = (snapshot: ArchitectureSnapshot): number => {
const { metrics, modules } = snapshot;
const baseCoupling = Math.min((metrics.averageCoupling / 3) * 100, 100);
const circularPenalty = metrics.circularDependencyCount * 5;
const highDepModules = modules.filter((m) => m.dependencies.length > 5).length;
const highDepPenalty = (highDepModules / modules.length) * 20; // ← 0/0 = NaN when modules is empty
return Math.round(Math.min(100, baseCoupling + circularPenalty + highDepPenalty));
};
Why it's a bug
For an empty-modules snapshot:
highDepModules = 0, modules.length = 0 → highDepPenalty = (0 / 0) * 20 = NaN
coupling = Math.round(Math.min(100, finite + finite + NaN)) = NaN
Then in calculateRepositoryHealth:
const healthScore = Math.round(modularity*0.3 + (100 - coupling)*0.3 + cohesion*0.2 + (100 - complexity)*0.2);
health: Math.max(0, Math.min(100, healthScore)) // Math.max(0, NaN) === NaN
Math.max(0, NaN) returns NaN, so health is NaN.
Reproduction
const snapshot = {
modules: [],
metrics: { averageCoupling: 0, circularDependencyCount: 0, /* …other fields 0 */ },
// …
};
calculateCouplingScore(snapshot); // → NaN
calculateRepositoryHealth(snapshot).health; // → NaN
Impact
calculateRepositoryHealth is consumed by src/services/architectureDriftService.ts and rendered by src/components/repository/ArchitecturalDriftDetector.tsx (health score + status/color). A repository whose analysis yields zero modules (empty/tiny repo, or a parse that produced no modules) shows a broken NaN health value in the UI instead of a sensible number. The fact that the sibling functions all guard modules.length === 0 shows the empty case is an expected input that this function simply forgot to handle.
Proposed fix
Guard the division so an empty modules array contributes 0 high-dependency penalty:
const highDepPenalty =
modules.length > 0 ? (highDepModules / modules.length) * 20 : 0;
I'll add a unit test for complexityCalculator (currently untested) covering the empty-modules case and a non-empty sanity check. I'd like to work on this.
/assign gssoc
Description
calculateCouplingScoreinsrc/utils/complexityCalculator.tsdivides bymodules.lengthwithout guarding against an emptymodulesarray. When a snapshot has zero modules, this produces0 / 0 = NaN, which propagates throughcalculateRepositoryHealthand makes the repository health scoreNaN.Every sibling scoring function in the same file guards the empty case —
calculateModularityScore(if (modules.length === 0) return 0) andcalculateCohesionScore(if (modules.length === 0) return 100) — butcalculateCouplingScoredoes not.Location
src/utils/complexityCalculator.ts,calculateCouplingScore(lines ~46–54):Why it's a bug
For an empty-modules snapshot:
highDepModules = 0,modules.length = 0→highDepPenalty = (0 / 0) * 20 = NaNcoupling = Math.round(Math.min(100, finite + finite + NaN)) = NaNThen in
calculateRepositoryHealth:Math.max(0, NaN)returnsNaN, sohealthisNaN.Reproduction
Impact
calculateRepositoryHealthis consumed bysrc/services/architectureDriftService.tsand rendered bysrc/components/repository/ArchitecturalDriftDetector.tsx(health score + status/color). A repository whose analysis yields zero modules (empty/tiny repo, or a parse that produced no modules) shows a brokenNaNhealth value in the UI instead of a sensible number. The fact that the sibling functions all guardmodules.length === 0shows the empty case is an expected input that this function simply forgot to handle.Proposed fix
Guard the division so an empty
modulesarray contributes0high-dependency penalty:I'll add a unit test for
complexityCalculator(currently untested) covering the empty-modules case and a non-empty sanity check. I'd like to work on this./assign gssoc