From e9aaa708fec57ba9d78ccc38fb5ab5174772aefc Mon Sep 17 00:00:00 2001 From: imfozilbek Date: Fri, 5 Dec 2025 15:43:24 +0500 Subject: [PATCH] feat(ipuaro): add impact score to initial context Add High Impact Files section to initial context showing which files are most critical based on percentage of codebase that depends on them. Changes: - Add impactScore field to FileMeta (0-100) - Add calculateImpactScore() helper function - Update MetaAnalyzer to compute impact scores - Add formatHighImpactFiles() to prompts.ts - Add includeHighImpactFiles config option (default: true) - 28 new tests (1826 total) --- packages/ipuaro/CHANGELOG.md | 58 +++ packages/ipuaro/ROADMAP.md | 28 +- .../src/domain/value-objects/FileMeta.ts | 20 ++ .../infrastructure/indexer/MetaAnalyzer.ts | 8 + .../ipuaro/src/infrastructure/llm/prompts.ts | 77 ++++ .../ipuaro/src/shared/constants/config.ts | 1 + .../domain/value-objects/FileMeta.test.ts | 54 ++- .../unit/infrastructure/llm/prompts.test.ts | 340 ++++++++++++++++++ .../tests/unit/shared/context-config.test.ts | 32 ++ 9 files changed, 606 insertions(+), 12 deletions(-) diff --git a/packages/ipuaro/CHANGELOG.md b/packages/ipuaro/CHANGELOG.md index aad6689..db3907f 100644 --- a/packages/ipuaro/CHANGELOG.md +++ b/packages/ipuaro/CHANGELOG.md @@ -5,6 +5,64 @@ 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.29.0] - 2025-12-05 - Impact Score + +### Added + +- **High Impact Files in Initial Context (v0.29.0)** + - New `## High Impact Files` section in initial context + - Shows files with highest impact scores (percentage of codebase depending on them) + - Table format with File, Impact %, and Dependents count + - Files sorted by impact score descending + - Default: shows top 10 files with impact score >= 5% + +- **Impact Score Computation** + - New `impactScore: number` field in `FileMeta` (0-100) + - Formula: `(dependents.length / (totalFiles - 1)) * 100` + - Computed in `MetaAnalyzer.analyzeAll()` after all files analyzed + - New `calculateImpactScore()` helper function in FileMeta.ts + +- **Configuration Option** + - `includeHighImpactFiles: boolean` in ContextConfigSchema (default: `true`) + - `includeHighImpactFiles` option in `BuildContextOptions` + - Users can disable to save tokens: `context.includeHighImpactFiles: false` + +- **New Helper Function in prompts.ts** + - `formatHighImpactFiles()` - formats high impact files table for display + +### New Context Format + +``` +## High Impact Files + +| File | Impact | Dependents | +|------|--------|------------| +| utils/validation | 67% | 12 files | +| types/user | 45% | 8 files | +| services/user | 34% | 6 files | +``` + +### Technical Details + +- Total tests: 1826 passed (was 1798, +28 new tests) + - 9 new tests for calculateImpactScore() + - 14 new tests for formatHighImpactFiles() and buildInitialContext + - 5 new tests for includeHighImpactFiles config option +- Coverage: 97.52% lines, 91.3% branches, 98.63% functions +- 0 ESLint errors, 3 warnings (pre-existing complexity) +- Build successful + +### Notes + +This completes v0.29.0 of the Graph Metrics milestone: +- ✅ 0.27.0 - Inline Dependency Graph +- ✅ 0.28.0 - Circular Dependencies in Context +- ✅ 0.29.0 - Impact Score + +Next milestone: v0.30.0 - Transitive Dependencies Count + +--- + ## [0.28.0] - 2025-12-05 - Circular Dependencies in Context ### Added diff --git a/packages/ipuaro/ROADMAP.md b/packages/ipuaro/ROADMAP.md index c7a560a..932f22d 100644 --- a/packages/ipuaro/ROADMAP.md +++ b/packages/ipuaro/ROADMAP.md @@ -1950,10 +1950,10 @@ Enhance initial context for LLM: add function signatures, interface field types, --- -## Version 0.29.0 - Impact Score 📈 +## Version 0.29.0 - Impact Score 📈 ✅ **Priority:** MEDIUM -**Status:** Planned +**Status:** Complete (v0.29.0 released) ### Description @@ -1972,10 +1972,16 @@ Enhance initial context for LLM: add function signatures, interface field types, ``` **Changes:** -- [ ] Add `impactScore: number` to FileMeta (0-100) -- [ ] Compute in MetaAnalyzer: (transitiveDepByCount / totalFiles) * 100 -- [ ] Add `formatHighImpactFiles()` to prompts.ts -- [ ] Show top-10 high impact files +- [x] Add `impactScore: number` to FileMeta (0-100) +- [x] Compute in MetaAnalyzer: (dependents.length / (totalFiles - 1)) * 100 +- [x] Add `formatHighImpactFiles()` to prompts.ts +- [x] Show top-10 high impact files +- [x] Add `includeHighImpactFiles` config option (default: true) + +**Tests:** +- [x] Unit tests for calculateImpactScore (9 tests) +- [x] Unit tests for formatHighImpactFiles (14 tests) +- [x] Unit tests for includeHighImpactFiles config (5 tests) **Why:** LLM understands which files are critical for changes. @@ -2022,12 +2028,12 @@ interface FileMeta { - [x] Error handling complete ✅ (v0.16.0) - [ ] Performance optimized - [x] Documentation complete ✅ (v0.17.0) -- [x] Test coverage ≥91% branches, ≥95% lines/functions/statements ✅ (91.13% branches, 97.48% lines, 98.63% functions, 97.48% statements - 1798 tests) +- [x] Test coverage ≥91% branches, ≥95% lines/functions/statements ✅ (91.3% branches, 97.52% lines, 98.63% functions, 97.52% statements - 1826 tests) - [x] 0 ESLint errors ✅ - [x] Examples working ✅ (v0.18.0) - [x] CHANGELOG.md up to date ✅ - [x] Rich initial context (v0.24.0-v0.26.0) — function signatures, interface fields, enum values, decorators ✅ -- [ ] Graph metrics in context (v0.27.0-v0.30.0) — dependency graph ✅, circular deps ✅, impact score, transitive deps +- [ ] Graph metrics in context (v0.27.0-v0.30.0) — dependency graph ✅, circular deps ✅, impact score ✅, transitive deps --- @@ -2106,7 +2112,7 @@ sessions:list # List **Last Updated:** 2025-12-05 **Target Version:** 1.0.0 -**Current Version:** 0.28.0 -**Next Milestones:** v0.29.0 (Impact Score), v0.30.0 (Transitive Deps) +**Current Version:** 0.29.0 +**Next Milestones:** v0.30.0 (Transitive Deps), v1.0.0 (Production Ready) -> **Note:** Rich Initial Context complete ✅ (v0.24.0-v0.26.0). Graph Metrics in progress (v0.27.0 ✅, v0.28.0 ✅, v0.29.0-v0.30.0 pending) for 1.0.0 release. \ No newline at end of file +> **Note:** Rich Initial Context complete ✅ (v0.24.0-v0.26.0). Graph Metrics in progress (v0.27.0 ✅, v0.28.0 ✅, v0.29.0 ✅, v0.30.0 pending) for 1.0.0 release. \ No newline at end of file diff --git a/packages/ipuaro/src/domain/value-objects/FileMeta.ts b/packages/ipuaro/src/domain/value-objects/FileMeta.ts index 49fd6bb..13e94ee 100644 --- a/packages/ipuaro/src/domain/value-objects/FileMeta.ts +++ b/packages/ipuaro/src/domain/value-objects/FileMeta.ts @@ -26,6 +26,8 @@ export interface FileMeta { isEntryPoint: boolean /** File type classification */ fileType: "source" | "test" | "config" | "types" | "unknown" + /** Impact score (0-100): percentage of codebase that depends on this file */ + impactScore: number } export function createFileMeta(partial: Partial = {}): FileMeta { @@ -41,6 +43,7 @@ export function createFileMeta(partial: Partial = {}): FileMeta { isHub: false, isEntryPoint: false, fileType: "unknown", + impactScore: 0, ...partial, } } @@ -48,3 +51,20 @@ export function createFileMeta(partial: Partial = {}): FileMeta { export function isHubFile(dependentCount: number): boolean { return dependentCount > 5 } + +/** + * Calculate impact score based on number of dependents and total files. + * Impact score represents what percentage of the codebase depends on this file. + * @param dependentCount - Number of files that depend on this file + * @param totalFiles - Total number of files in the project + * @returns Impact score from 0 to 100 + */ +export function calculateImpactScore(dependentCount: number, totalFiles: number): number { + if (totalFiles <= 1) { + return 0 + } + // Exclude the file itself from the total + const maxPossibleDependents = totalFiles - 1 + const score = (dependentCount / maxPossibleDependents) * 100 + return Math.round(Math.min(100, score)) +} diff --git a/packages/ipuaro/src/infrastructure/indexer/MetaAnalyzer.ts b/packages/ipuaro/src/infrastructure/indexer/MetaAnalyzer.ts index 8c06333..fb959ca 100644 --- a/packages/ipuaro/src/infrastructure/indexer/MetaAnalyzer.ts +++ b/packages/ipuaro/src/infrastructure/indexer/MetaAnalyzer.ts @@ -1,5 +1,6 @@ import * as path from "node:path" import { + calculateImpactScore, type ComplexityMetrics, createFileMeta, type FileMeta, @@ -430,6 +431,7 @@ export class MetaAnalyzer { /** * Batch analyze multiple files. + * Computes impact scores after all files are analyzed. */ analyzeAll(files: Map): Map { const allASTs = new Map() @@ -443,6 +445,12 @@ export class MetaAnalyzer { results.set(filePath, meta) } + // Compute impact scores now that we know total file count + const totalFiles = results.size + for (const [, meta] of results) { + meta.impactScore = calculateImpactScore(meta.dependents.length, totalFiles) + } + return results } } diff --git a/packages/ipuaro/src/infrastructure/llm/prompts.ts b/packages/ipuaro/src/infrastructure/llm/prompts.ts index 48d3c7b..20776b1 100644 --- a/packages/ipuaro/src/infrastructure/llm/prompts.ts +++ b/packages/ipuaro/src/infrastructure/llm/prompts.ts @@ -18,6 +18,7 @@ export interface BuildContextOptions { includeSignatures?: boolean includeDepsGraph?: boolean includeCircularDeps?: boolean + includeHighImpactFiles?: boolean circularDeps?: string[][] } @@ -132,6 +133,7 @@ export function buildInitialContext( const includeSignatures = options?.includeSignatures ?? true const includeDepsGraph = options?.includeDepsGraph ?? true const includeCircularDeps = options?.includeCircularDeps ?? true + const includeHighImpactFiles = options?.includeHighImpactFiles ?? true sections.push(formatProjectHeader(structure)) sections.push(formatDirectoryTree(structure)) @@ -144,6 +146,13 @@ export function buildInitialContext( } } + if (includeHighImpactFiles && metas && metas.size > 0) { + const highImpactSection = formatHighImpactFiles(metas) + if (highImpactSection) { + sections.push(highImpactSection) + } + } + if (includeCircularDeps && options?.circularDeps && options.circularDeps.length > 0) { const circularDepsSection = formatCircularDeps(options.circularDeps) if (circularDepsSection) { @@ -568,6 +577,74 @@ export function formatCircularDeps(cycles: string[][]): string | null { return lines.join("\n") } +/** + * Format high impact files table for display in context. + * Shows files with highest impact scores (most dependents). + * + * Format: + * ## High Impact Files + * | File | Impact | Dependents | + * |------|--------|------------| + * | src/utils/validation.ts | 67% | 12 files | + * + * @param metas - Map of file paths to their metadata + * @param limit - Maximum number of files to show (default: 10) + * @param minImpact - Minimum impact score to include (default: 5) + */ +export function formatHighImpactFiles( + metas: Map, + limit = 10, + minImpact = 5, +): string | null { + if (metas.size === 0) { + return null + } + + // Collect files with impact score >= minImpact + const impactFiles: { path: string; impact: number; dependents: number }[] = [] + + for (const [path, meta] of metas) { + if (meta.impactScore >= minImpact) { + impactFiles.push({ + path, + impact: meta.impactScore, + dependents: meta.dependents.length, + }) + } + } + + if (impactFiles.length === 0) { + return null + } + + // Sort by impact score descending, then by path + impactFiles.sort((a, b) => { + if (a.impact !== b.impact) { + return b.impact - a.impact + } + return a.path.localeCompare(b.path) + }) + + // Take top N files + const topFiles = impactFiles.slice(0, limit) + + const lines: string[] = [ + "## High Impact Files", + "", + "| File | Impact | Dependents |", + "|------|--------|------------|", + ] + + 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} |`) + } + + return lines.join("\n") +} + /** * Format line range for display. */ diff --git a/packages/ipuaro/src/shared/constants/config.ts b/packages/ipuaro/src/shared/constants/config.ts index 365f0c9..cdd0d3a 100644 --- a/packages/ipuaro/src/shared/constants/config.ts +++ b/packages/ipuaro/src/shared/constants/config.ts @@ -117,6 +117,7 @@ export const ContextConfigSchema = z.object({ includeSignatures: z.boolean().default(true), includeDepsGraph: z.boolean().default(true), includeCircularDeps: z.boolean().default(true), + includeHighImpactFiles: z.boolean().default(true), }) /** diff --git a/packages/ipuaro/tests/unit/domain/value-objects/FileMeta.test.ts b/packages/ipuaro/tests/unit/domain/value-objects/FileMeta.test.ts index 511c7ae..3edbbfb 100644 --- a/packages/ipuaro/tests/unit/domain/value-objects/FileMeta.test.ts +++ b/packages/ipuaro/tests/unit/domain/value-objects/FileMeta.test.ts @@ -1,5 +1,9 @@ import { describe, it, expect } from "vitest" -import { createFileMeta, isHubFile } from "../../../../src/domain/value-objects/FileMeta.js" +import { + calculateImpactScore, + createFileMeta, + isHubFile, +} from "../../../../src/domain/value-objects/FileMeta.js" describe("FileMeta", () => { describe("createFileMeta", () => { @@ -15,6 +19,7 @@ describe("FileMeta", () => { expect(meta.isHub).toBe(false) expect(meta.isEntryPoint).toBe(false) expect(meta.fileType).toBe("unknown") + expect(meta.impactScore).toBe(0) }) it("should merge partial values", () => { @@ -42,4 +47,51 @@ describe("FileMeta", () => { expect(isHubFile(0)).toBe(false) }) }) + + describe("calculateImpactScore", () => { + it("should return 0 for file with 0 dependents", () => { + expect(calculateImpactScore(0, 10)).toBe(0) + }) + + it("should return 0 when totalFiles is 0", () => { + expect(calculateImpactScore(5, 0)).toBe(0) + }) + + it("should return 0 when totalFiles is 1", () => { + expect(calculateImpactScore(0, 1)).toBe(0) + }) + + it("should calculate correct percentage", () => { + // 5 dependents out of 10 files (excluding itself = 9 possible) + // 5/9 * 100 = 55.56 → rounded to 56 + expect(calculateImpactScore(5, 10)).toBe(56) + }) + + it("should return 100 when all other files depend on it", () => { + // 9 dependents out of 10 files (9 possible dependents) + expect(calculateImpactScore(9, 10)).toBe(100) + }) + + it("should cap at 100", () => { + // Edge case: more dependents than possible (shouldn't happen normally) + expect(calculateImpactScore(20, 10)).toBe(100) + }) + + it("should round the percentage", () => { + // 1 dependent out of 3 files (2 possible) + // 1/2 * 100 = 50 + expect(calculateImpactScore(1, 3)).toBe(50) + }) + + it("should calculate impact for small projects", () => { + // 1 dependent out of 2 files (1 possible) + expect(calculateImpactScore(1, 2)).toBe(100) + }) + + it("should calculate impact for larger projects", () => { + // 50 dependents out of 100 files (99 possible) + // 50/99 * 100 = 50.51 → rounded to 51 + expect(calculateImpactScore(50, 100)).toBe(51) + }) + }) }) diff --git a/packages/ipuaro/tests/unit/infrastructure/llm/prompts.test.ts b/packages/ipuaro/tests/unit/infrastructure/llm/prompts.test.ts index 943d92b..e364745 100644 --- a/packages/ipuaro/tests/unit/infrastructure/llm/prompts.test.ts +++ b/packages/ipuaro/tests/unit/infrastructure/llm/prompts.test.ts @@ -6,6 +6,7 @@ import { truncateContext, formatDependencyGraph, formatCircularDeps, + formatHighImpactFiles, type ProjectStructure, } from "../../../../src/infrastructure/llm/prompts.js" import type { FileAST } from "../../../../src/domain/value-objects/FileAST.js" @@ -2395,6 +2396,345 @@ describe("prompts", () => { }) }) + describe("high impact files (0.29.0)", () => { + describe("formatHighImpactFiles", () => { + it("should return null for empty metas", () => { + const metas = new Map() + + const result = formatHighImpactFiles(metas) + + expect(result).toBeNull() + }) + + it("should return null when no files have impact score >= minImpact", () => { + const metas = new Map([ + [ + "src/low.ts", + { + complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 }, + dependencies: [], + dependents: [], + isHub: false, + isEntryPoint: false, + fileType: "source", + impactScore: 2, + }, + ], + ]) + + const result = formatHighImpactFiles(metas) + + expect(result).toBeNull() + }) + + it("should format file with high impact score", () => { + const metas = new Map([ + [ + "src/utils/validation.ts", + { + complexity: { loc: 50, nesting: 2, cyclomaticComplexity: 5, score: 30 }, + dependencies: [], + dependents: [ + "a.ts", + "b.ts", + "c.ts", + "d.ts", + "e.ts", + "f.ts", + "g.ts", + "h.ts", + "i.ts", + "j.ts", + "k.ts", + "l.ts", + ], + isHub: true, + isEntryPoint: false, + fileType: "source", + impactScore: 67, + }, + ], + ]) + + const result = formatHighImpactFiles(metas) + + expect(result).toContain("## High Impact Files") + expect(result).toContain("| File | Impact | Dependents |") + expect(result).toContain("| utils/validation | 67% | 12 files |") + }) + + it("should sort by impact score descending", () => { + const metas = new Map([ + [ + "src/low.ts", + { + complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 }, + dependencies: [], + dependents: ["a.ts"], + isHub: false, + isEntryPoint: false, + fileType: "source", + impactScore: 10, + }, + ], + [ + "src/high.ts", + { + complexity: { loc: 20, nesting: 1, cyclomaticComplexity: 1, score: 10 }, + dependencies: [], + dependents: ["a.ts", "b.ts", "c.ts", "d.ts", "e.ts"], + isHub: false, + isEntryPoint: false, + fileType: "source", + impactScore: 50, + }, + ], + ]) + + const result = formatHighImpactFiles(metas) + + expect(result).not.toBeNull() + const lines = result!.split("\n") + const highIndex = lines.findIndex((l) => l.includes("high")) + const lowIndex = lines.findIndex((l) => l.includes("low")) + expect(highIndex).toBeLessThan(lowIndex) + }) + + it("should limit to top N files", () => { + const metas = new Map() + for (let i = 0; i < 20; i++) { + metas.set(`src/file${String(i)}.ts`, { + complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 }, + dependencies: [], + dependents: ["a.ts"], + isHub: false, + isEntryPoint: false, + fileType: "source", + impactScore: 10 + i, + }) + } + + const result = formatHighImpactFiles(metas, 5) + + expect(result).not.toBeNull() + const dataLines = result! + .split("\n") + .filter((l) => l.startsWith("| ") && l.includes("%")) + expect(dataLines).toHaveLength(5) + }) + + it("should filter by minImpact", () => { + const metas = new Map([ + [ + "src/high.ts", + { + complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 }, + dependencies: [], + dependents: ["a.ts", "b.ts", "c.ts"], + isHub: false, + isEntryPoint: false, + fileType: "source", + impactScore: 30, + }, + ], + [ + "src/low.ts", + { + complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 }, + dependencies: [], + dependents: ["a.ts"], + isHub: false, + isEntryPoint: false, + fileType: "source", + impactScore: 5, + }, + ], + ]) + + const result = formatHighImpactFiles(metas, 10, 20) + + expect(result).not.toBeNull() + expect(result).toContain("high") + 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([ + [ + "src/services/user.ts", + { + complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 }, + dependencies: [], + dependents: ["a.ts", "b.ts"], + isHub: false, + isEntryPoint: false, + fileType: "source", + impactScore: 20, + }, + ], + ]) + + const result = formatHighImpactFiles(metas) + + expect(result).toContain("services/user") + expect(result).not.toContain("src/") + }) + + it("should remove file extensions", () => { + const metas = new Map([ + [ + "lib/utils.ts", + { + complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 }, + dependencies: [], + dependents: ["a.ts", "b.ts"], + isHub: false, + isEntryPoint: false, + fileType: "source", + impactScore: 20, + }, + ], + ]) + + const result = formatHighImpactFiles(metas) + + expect(result).toContain("lib/utils") + expect(result).not.toContain(".ts") + }) + }) + + describe("buildInitialContext with includeHighImpactFiles", () => { + const structure: ProjectStructure = { + name: "test-project", + rootPath: "/test", + files: ["src/index.ts"], + directories: ["src"], + } + + const asts = new Map([ + [ + "src/index.ts", + { + imports: [], + exports: [], + functions: [], + classes: [], + interfaces: [], + typeAliases: [], + parseError: false, + }, + ], + ]) + + it("should include high impact files by default", () => { + const metas = new Map([ + [ + "src/index.ts", + { + complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 }, + dependencies: [], + dependents: ["a.ts", "b.ts"], + isHub: false, + isEntryPoint: true, + fileType: "source", + impactScore: 20, + }, + ], + ]) + + const context = buildInitialContext(structure, asts, metas) + + expect(context).toContain("## High Impact Files") + }) + + it("should exclude high impact files when includeHighImpactFiles is false", () => { + const metas = new Map([ + [ + "src/index.ts", + { + complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 }, + dependencies: [], + dependents: ["a.ts", "b.ts"], + isHub: false, + isEntryPoint: true, + fileType: "source", + impactScore: 20, + }, + ], + ]) + + const context = buildInitialContext(structure, asts, metas, { + includeHighImpactFiles: false, + }) + + expect(context).not.toContain("## High Impact Files") + }) + + it("should not include high impact files when metas is undefined", () => { + const context = buildInitialContext(structure, asts, undefined, { + includeHighImpactFiles: true, + }) + + expect(context).not.toContain("## High Impact Files") + }) + + it("should not include high impact files when metas is empty", () => { + const emptyMetas = new Map() + + const context = buildInitialContext(structure, asts, emptyMetas, { + includeHighImpactFiles: true, + }) + + expect(context).not.toContain("## High Impact Files") + }) + + it("should not include high impact files when no files have high impact", () => { + const metas = new Map([ + [ + "src/index.ts", + { + complexity: { loc: 10, nesting: 1, cyclomaticComplexity: 1, score: 10 }, + dependencies: [], + dependents: [], + isHub: false, + isEntryPoint: true, + fileType: "source", + impactScore: 0, + }, + ], + ]) + + const context = buildInitialContext(structure, asts, metas, { + includeHighImpactFiles: true, + }) + + expect(context).not.toContain("## High Impact Files") + }) + }) + }) + describe("circular dependencies (0.28.0)", () => { describe("formatCircularDeps", () => { it("should return null for empty array", () => { diff --git a/packages/ipuaro/tests/unit/shared/context-config.test.ts b/packages/ipuaro/tests/unit/shared/context-config.test.ts index b86d9ae..62de6ae 100644 --- a/packages/ipuaro/tests/unit/shared/context-config.test.ts +++ b/packages/ipuaro/tests/unit/shared/context-config.test.ts @@ -18,6 +18,7 @@ describe("ContextConfigSchema", () => { includeSignatures: true, includeDepsGraph: true, includeCircularDeps: true, + includeHighImpactFiles: true, }) }) @@ -32,6 +33,7 @@ describe("ContextConfigSchema", () => { includeSignatures: true, includeDepsGraph: true, includeCircularDeps: true, + includeHighImpactFiles: true, }) }) }) @@ -171,6 +173,7 @@ describe("ContextConfigSchema", () => { includeSignatures: true, includeDepsGraph: true, includeCircularDeps: true, + includeHighImpactFiles: true, }) }) @@ -187,6 +190,7 @@ describe("ContextConfigSchema", () => { includeSignatures: true, includeDepsGraph: true, includeCircularDeps: true, + includeHighImpactFiles: true, }) }) @@ -204,6 +208,7 @@ describe("ContextConfigSchema", () => { includeSignatures: true, includeDepsGraph: true, includeCircularDeps: true, + includeHighImpactFiles: true, }) }) }) @@ -218,6 +223,7 @@ describe("ContextConfigSchema", () => { includeSignatures: false, includeDepsGraph: false, includeCircularDeps: false, + includeHighImpactFiles: false, } const result = ContextConfigSchema.parse(config) @@ -233,6 +239,7 @@ describe("ContextConfigSchema", () => { includeSignatures: true, includeDepsGraph: true, includeCircularDeps: true, + includeHighImpactFiles: true, } const result = ContextConfigSchema.parse(config) @@ -314,4 +321,29 @@ describe("ContextConfigSchema", () => { expect(() => ContextConfigSchema.parse({ includeCircularDeps: 1 })).toThrow() }) }) + + describe("includeHighImpactFiles", () => { + it("should accept true", () => { + const result = ContextConfigSchema.parse({ includeHighImpactFiles: true }) + expect(result.includeHighImpactFiles).toBe(true) + }) + + it("should accept false", () => { + const result = ContextConfigSchema.parse({ includeHighImpactFiles: false }) + expect(result.includeHighImpactFiles).toBe(false) + }) + + it("should default to true", () => { + const result = ContextConfigSchema.parse({}) + expect(result.includeHighImpactFiles).toBe(true) + }) + + it("should reject non-boolean", () => { + expect(() => ContextConfigSchema.parse({ includeHighImpactFiles: "true" })).toThrow() + }) + + it("should reject number", () => { + expect(() => ContextConfigSchema.parse({ includeHighImpactFiles: 1 })).toThrow() + }) + }) })