From 2e84472e495ec5b0cce42a1004222165d0b0b199 Mon Sep 17 00:00:00 2001 From: imfozilbek Date: Fri, 5 Dec 2025 16:16:22 +0500 Subject: [PATCH] feat(ipuaro): display transitive counts in High Impact Files table - Change table header to include Direct and Transitive columns - Sort by transitive count first, then by impact score - Update tests for new table format --- packages/ipuaro/CHANGELOG.md | 17 ++++++ .../ipuaro/src/infrastructure/llm/prompts.ts | 29 +++++++---- .../unit/infrastructure/llm/prompts.test.ts | 52 +++++++++---------- 3 files changed, 63 insertions(+), 35 deletions(-) diff --git a/packages/ipuaro/CHANGELOG.md b/packages/ipuaro/CHANGELOG.md index 427a69f..4d1587b 100644 --- a/packages/ipuaro/CHANGELOG.md +++ b/packages/ipuaro/CHANGELOG.md @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.30.1] - 2025-12-05 - Display Transitive Counts in Context + +### Changed + +- **High Impact Files table now includes transitive counts** + - Table header changed from `| File | Impact | Dependents |` to `| File | Impact | Direct | Transitive |` + - Shows both direct dependent count and transitive dependent count + - Sorting changed: now sorts by transitive count first, then by impact score + - Example: `| utils/validation | 67% | 12 | 24 |` + +### Technical Details + +- Total tests: 1839 passed +- 0 ESLint errors, 3 warnings (pre-existing complexity) + +--- + ## [0.30.0] - 2025-12-05 - Transitive Dependencies Count ### Added diff --git a/packages/ipuaro/src/infrastructure/llm/prompts.ts b/packages/ipuaro/src/infrastructure/llm/prompts.ts index 20776b1..9519ba8 100644 --- a/packages/ipuaro/src/infrastructure/llm/prompts.ts +++ b/packages/ipuaro/src/infrastructure/llm/prompts.ts @@ -580,12 +580,13 @@ export function formatCircularDeps(cycles: string[][]): string | null { /** * Format high impact files table for display in context. * Shows files with highest impact scores (most dependents). + * Includes both direct and transitive dependent counts. * * Format: * ## High Impact Files - * | File | Impact | Dependents | - * |------|--------|------------| - * | src/utils/validation.ts | 67% | 12 files | + * | File | Impact | Direct | Transitive | + * |------|--------|--------|------------| + * | src/utils/validation.ts | 67% | 12 | 24 | * * @param metas - Map of file paths to their metadata * @param limit - Maximum number of files to show (default: 10) @@ -601,7 +602,12 @@ export function formatHighImpactFiles( } // Collect files with impact score >= minImpact - const impactFiles: { path: string; impact: number; dependents: number }[] = [] + const impactFiles: { + path: string + impact: number + dependents: number + transitive: number + }[] = [] for (const [path, meta] of metas) { if (meta.impactScore >= minImpact) { @@ -609,6 +615,7 @@ export function formatHighImpactFiles( path, impact: meta.impactScore, dependents: meta.dependents.length, + transitive: meta.transitiveDepCount, }) } } @@ -617,8 +624,11 @@ export function formatHighImpactFiles( return null } - // Sort by impact score descending, then by path + // Sort by transitive count descending, then by impact, then by path impactFiles.sort((a, b) => { + if (a.transitive !== b.transitive) { + return b.transitive - a.transitive + } if (a.impact !== b.impact) { return b.impact - a.impact } @@ -631,15 +641,16 @@ export function formatHighImpactFiles( const lines: string[] = [ "## High Impact Files", "", - "| File | Impact | Dependents |", - "|------|--------|------------|", + "| File | Impact | Direct | Transitive |", + "|------|--------|--------|------------|", ] for (const file of topFiles) { const shortPath = shortenPath(file.path) const impact = `${String(file.impact)}%` - const dependents = file.dependents === 1 ? "1 file" : `${String(file.dependents)} files` - lines.push(`| ${shortPath} | ${impact} | ${dependents} |`) + const direct = String(file.dependents) + const transitive = String(file.transitive) + lines.push(`| ${shortPath} | ${impact} | ${direct} | ${transitive} |`) } return lines.join("\n") diff --git a/packages/ipuaro/tests/unit/infrastructure/llm/prompts.test.ts b/packages/ipuaro/tests/unit/infrastructure/llm/prompts.test.ts index e364745..03c30b0 100644 --- a/packages/ipuaro/tests/unit/infrastructure/llm/prompts.test.ts +++ b/packages/ipuaro/tests/unit/infrastructure/llm/prompts.test.ts @@ -2418,6 +2418,8 @@ describe("prompts", () => { isEntryPoint: false, fileType: "source", impactScore: 2, + transitiveDepCount: 0, + transitiveDepByCount: 0, }, ], ]) @@ -2427,7 +2429,7 @@ describe("prompts", () => { expect(result).toBeNull() }) - it("should format file with high impact score", () => { + it("should format file with high impact score and transitive counts", () => { const metas = new Map([ [ "src/utils/validation.ts", @@ -2452,6 +2454,8 @@ describe("prompts", () => { isEntryPoint: false, fileType: "source", impactScore: 67, + transitiveDepCount: 24, + transitiveDepByCount: 0, }, ], ]) @@ -2459,11 +2463,11 @@ describe("prompts", () => { const result = formatHighImpactFiles(metas) expect(result).toContain("## High Impact Files") - expect(result).toContain("| File | Impact | Dependents |") - expect(result).toContain("| utils/validation | 67% | 12 files |") + expect(result).toContain("| File | Impact | Direct | Transitive |") + expect(result).toContain("| utils/validation | 67% | 12 | 24 |") }) - it("should sort by impact score descending", () => { + it("should sort by transitive count descending, then by impact", () => { const metas = new Map([ [ "src/low.ts", @@ -2475,6 +2479,8 @@ describe("prompts", () => { isEntryPoint: false, fileType: "source", impactScore: 10, + transitiveDepCount: 5, + transitiveDepByCount: 0, }, ], [ @@ -2487,6 +2493,8 @@ describe("prompts", () => { isEntryPoint: false, fileType: "source", impactScore: 50, + transitiveDepCount: 15, + transitiveDepByCount: 0, }, ], ]) @@ -2511,6 +2519,8 @@ describe("prompts", () => { isEntryPoint: false, fileType: "source", impactScore: 10 + i, + transitiveDepCount: i, + transitiveDepByCount: 0, }) } @@ -2535,6 +2545,8 @@ describe("prompts", () => { isEntryPoint: false, fileType: "source", impactScore: 30, + transitiveDepCount: 5, + transitiveDepByCount: 0, }, ], [ @@ -2547,6 +2559,8 @@ describe("prompts", () => { isEntryPoint: false, fileType: "source", impactScore: 5, + transitiveDepCount: 1, + transitiveDepByCount: 0, }, ], ]) @@ -2558,28 +2572,6 @@ describe("prompts", () => { expect(result).not.toContain("low") }) - it("should show singular 'file' for 1 dependent", () => { - const metas = new Map([ - [ - "src/single.ts", - { - complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 }, - dependencies: [], - dependents: ["a.ts"], - isHub: false, - isEntryPoint: false, - fileType: "source", - impactScore: 10, - }, - ], - ]) - - const result = formatHighImpactFiles(metas) - - expect(result).toContain("1 file") - expect(result).not.toContain("1 files") - }) - it("should shorten src/ prefix", () => { const metas = new Map([ [ @@ -2592,6 +2584,8 @@ describe("prompts", () => { isEntryPoint: false, fileType: "source", impactScore: 20, + transitiveDepCount: 5, + transitiveDepByCount: 0, }, ], ]) @@ -2614,6 +2608,8 @@ describe("prompts", () => { isEntryPoint: false, fileType: "source", impactScore: 20, + transitiveDepCount: 3, + transitiveDepByCount: 0, }, ], ]) @@ -2660,6 +2656,8 @@ describe("prompts", () => { isEntryPoint: true, fileType: "source", impactScore: 20, + transitiveDepCount: 5, + transitiveDepByCount: 0, }, ], ]) @@ -2681,6 +2679,8 @@ describe("prompts", () => { isEntryPoint: true, fileType: "source", impactScore: 20, + transitiveDepCount: 5, + transitiveDepByCount: 0, }, ], ])