@aiready/context-analyzer 0.21.22 → 0.21.24
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/.turbo/turbo-build.log +26 -25
- package/.turbo/turbo-lint.log +5 -5
- package/.turbo/turbo-test.log +91 -41
- package/coverage/clover.xml +2615 -1242
- package/coverage/coverage-final.json +30 -13
- package/coverage/dist/chunk-64U3PNO3.mjs.html +367 -0
- package/coverage/dist/chunk-J3MUOWHC.mjs.html +5326 -0
- package/coverage/dist/index.html +146 -0
- package/coverage/{classifier.ts.html → dist/index.mjs.html} +537 -912
- package/coverage/index.html +84 -189
- package/coverage/src/analyzer.ts.html +88 -0
- package/coverage/src/analyzers/index.html +116 -0
- package/coverage/src/analyzers/python-context.ts.html +910 -0
- package/coverage/{ast-utils.ts.html → src/ast-utils.ts.html} +84 -54
- package/coverage/src/classifier.ts.html +892 -0
- package/coverage/src/classify/classification-patterns.ts.html +307 -0
- package/coverage/src/classify/file-classifiers.ts.html +973 -0
- package/coverage/src/classify/index.html +131 -0
- package/coverage/{cluster-detector.ts.html → src/cluster-detector.ts.html} +154 -91
- package/coverage/{defaults.ts.html → src/defaults.ts.html} +74 -65
- package/coverage/{graph-builder.ts.html → src/graph-builder.ts.html} +268 -229
- package/coverage/src/index.html +341 -0
- package/coverage/{index.ts.html → src/index.ts.html} +70 -13
- package/coverage/{scoring.ts.html → src/issue-analyzer.ts.html} +201 -261
- package/coverage/src/mapper.ts.html +439 -0
- package/coverage/{metrics.ts.html → src/metrics.ts.html} +201 -132
- package/coverage/src/orchestrator.ts.html +493 -0
- package/coverage/{provider.ts.html → src/provider.ts.html} +21 -21
- package/coverage/{remediation.ts.html → src/remediation.ts.html} +112 -52
- package/coverage/src/report/console-report.ts.html +415 -0
- package/coverage/src/report/html-report.ts.html +361 -0
- package/coverage/src/report/index.html +146 -0
- package/coverage/src/report/interactive-setup.ts.html +373 -0
- package/coverage/src/scoring.ts.html +895 -0
- package/coverage/src/semantic/co-usage.ts.html +340 -0
- package/coverage/src/semantic/consolidation.ts.html +223 -0
- package/coverage/src/semantic/domain-inference.ts.html +859 -0
- package/coverage/src/semantic/index.html +161 -0
- package/coverage/src/semantic/type-graph.ts.html +163 -0
- package/coverage/{summary.ts.html → src/summary.ts.html} +155 -275
- package/coverage/{types.ts.html → src/types.ts.html} +133 -31
- package/coverage/src/utils/dependency-graph-utils.ts.html +463 -0
- package/coverage/src/utils/index.html +131 -0
- package/coverage/src/utils/string-utils.ts.html +148 -0
- package/dist/chunk-AMPK6SWS.mjs +1754 -0
- package/dist/chunk-BHCRDEE4.mjs +1745 -0
- package/dist/chunk-IKRP7ECY.mjs +1754 -0
- package/dist/chunk-J3MUOWHC.mjs +1747 -0
- package/dist/chunk-TWWPY7FD.mjs +1754 -0
- package/dist/chunk-Z5WY6A4P.mjs +1754 -0
- package/dist/cli.js +77 -185
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +73 -181
- package/dist/index.mjs +1 -1
- package/dist/python-context-BWDC4E5Z.mjs +162 -0
- package/package.json +3 -3
- package/src/__tests__/analyzer.test.ts +14 -14
- package/src/__tests__/auto-detection.test.ts +16 -16
- package/src/__tests__/consolidation.test.ts +247 -0
- package/src/__tests__/defaults.test.ts +121 -0
- package/src/__tests__/domain-inference.test.ts +420 -0
- package/src/__tests__/fragmentation-coupling.test.ts +4 -4
- package/src/__tests__/issue-analyzer.test.ts +155 -0
- package/src/__tests__/orchestrator.test.ts +143 -0
- package/src/__tests__/python-context.test.ts +100 -0
- package/src/__tests__/report/console-report.test.ts +292 -0
- package/src/__tests__/report/html-report.test.ts +243 -0
- package/src/__tests__/scoring.test.ts +17 -11
- package/src/analyzers/python-context.ts +2 -2
- package/src/ast-utils.ts +3 -3
- package/src/graph-builder.ts +4 -4
- package/src/mapper.ts +6 -0
- package/src/orchestrator.ts +1 -1
- package/src/report/html-report.ts +73 -181
- package/coverage/analyzer.ts.html +0 -1369
- package/coverage/semantic-analysis.ts.html +0 -1201
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { analyzeContext, calculateCohesion } from '../orchestrator';
|
|
3
|
+
import * as core from '@aiready/core';
|
|
4
|
+
|
|
5
|
+
// Mock the core module
|
|
6
|
+
vi.mock('@aiready/core', async () => {
|
|
7
|
+
const actual = await vi.importActual('@aiready/core');
|
|
8
|
+
return {
|
|
9
|
+
...actual,
|
|
10
|
+
scanFiles: vi.fn(),
|
|
11
|
+
readFileContent: vi.fn(),
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('analyzeContext', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should analyze TypeScript files and return results', async () => {
|
|
21
|
+
const mockFiles = ['src/file1.ts', 'src/file2.ts'];
|
|
22
|
+
const mockContents: Record<string, string> = {
|
|
23
|
+
'src/file1.ts': `
|
|
24
|
+
import { b } from './file2';
|
|
25
|
+
export const a = 1;
|
|
26
|
+
`,
|
|
27
|
+
'src/file2.ts': `
|
|
28
|
+
export const b = 2;
|
|
29
|
+
`,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
vi.mocked(core.scanFiles).mockResolvedValue(mockFiles);
|
|
33
|
+
vi.mocked(core.readFileContent).mockImplementation(async (path: string) => {
|
|
34
|
+
return mockContents[path] || '';
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const results = await analyzeContext({
|
|
38
|
+
rootDir: '.',
|
|
39
|
+
maxDepth: 5,
|
|
40
|
+
maxContextBudget: 25000,
|
|
41
|
+
minCohesion: 0.6,
|
|
42
|
+
maxFragmentation: 0.5,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(results.length).toBe(2);
|
|
46
|
+
expect(results[0].file).toBeDefined();
|
|
47
|
+
expect(results[0].importDepth).toBeDefined();
|
|
48
|
+
expect(results[0].contextBudget).toBeDefined();
|
|
49
|
+
expect(core.scanFiles).toHaveBeenCalled();
|
|
50
|
+
expect(core.readFileContent).toHaveBeenCalledTimes(2);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should handle empty file list', async () => {
|
|
54
|
+
vi.mocked(core.scanFiles).mockResolvedValue([]);
|
|
55
|
+
|
|
56
|
+
const results = await analyzeContext({
|
|
57
|
+
rootDir: '.',
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(results).toEqual([]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should filter Python files when not present', async () => {
|
|
64
|
+
const mockFiles = ['src/file1.ts', 'src/file2.py'];
|
|
65
|
+
vi.mocked(core.scanFiles).mockResolvedValue(mockFiles);
|
|
66
|
+
vi.mocked(core.readFileContent).mockResolvedValue('export const a = 1;');
|
|
67
|
+
|
|
68
|
+
const results = await analyzeContext({
|
|
69
|
+
rootDir: '.',
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Should only process TypeScript files
|
|
73
|
+
expect(results.length).toBe(1);
|
|
74
|
+
expect(results[0].file).toBe('src/file1.ts');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should use default options when not provided', async () => {
|
|
78
|
+
vi.mocked(core.scanFiles).mockResolvedValue([]);
|
|
79
|
+
vi.mocked(core.readFileContent).mockResolvedValue('');
|
|
80
|
+
|
|
81
|
+
const results = await analyzeContext({
|
|
82
|
+
rootDir: '.',
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(results).toEqual([]);
|
|
86
|
+
expect(core.scanFiles).toHaveBeenCalledWith(
|
|
87
|
+
expect.objectContaining({
|
|
88
|
+
rootDir: '.',
|
|
89
|
+
})
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should handle includeNodeModules option', async () => {
|
|
94
|
+
const mockFiles = ['node_modules/pkg/index.js', 'src/file.ts'];
|
|
95
|
+
vi.mocked(core.scanFiles).mockResolvedValue(mockFiles);
|
|
96
|
+
vi.mocked(core.readFileContent).mockResolvedValue('export const a = 1;');
|
|
97
|
+
|
|
98
|
+
const results = await analyzeContext({
|
|
99
|
+
rootDir: '.',
|
|
100
|
+
includeNodeModules: true,
|
|
101
|
+
exclude: ['**/node_modules/**'],
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// When includeNodeModules is true, node_modules should be processed
|
|
105
|
+
expect(results.length).toBe(2);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('calculateCohesion', () => {
|
|
110
|
+
it('should delegate to calculateEnhancedCohesion', () => {
|
|
111
|
+
const exports = [
|
|
112
|
+
{ name: 'getUser', type: 'function' as const, inferredDomain: 'user' },
|
|
113
|
+
{ name: 'updateUser', type: 'function' as const, inferredDomain: 'user' },
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
const cohesion = calculateCohesion(exports);
|
|
117
|
+
|
|
118
|
+
// Should return a valid cohesion score
|
|
119
|
+
expect(cohesion).toBeGreaterThan(0);
|
|
120
|
+
expect(cohesion).toBeLessThanOrEqual(1);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should handle empty exports', () => {
|
|
124
|
+
const cohesion = calculateCohesion([]);
|
|
125
|
+
expect(cohesion).toBe(1); // Empty exports should have perfect cohesion
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should pass through file path and options', () => {
|
|
129
|
+
const exports = [
|
|
130
|
+
{ name: 'test', type: 'function' as const, inferredDomain: 'test' },
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
// Test with file path
|
|
134
|
+
const cohesion = calculateCohesion(exports, 'src/test.ts');
|
|
135
|
+
expect(cohesion).toBe(1);
|
|
136
|
+
|
|
137
|
+
// Test with options
|
|
138
|
+
const cohesionWithOptions = calculateCohesion(exports, undefined, {
|
|
139
|
+
someOption: true,
|
|
140
|
+
});
|
|
141
|
+
expect(cohesionWithOptions).toBe(1);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import * as pythonContext from '../analyzers/python-context';
|
|
3
|
+
|
|
4
|
+
// Mock @aiready/core
|
|
5
|
+
vi.mock('@aiready/core', () => ({
|
|
6
|
+
getParser: vi.fn(async (filename: string) => {
|
|
7
|
+
if (filename.endsWith('.py')) {
|
|
8
|
+
return {
|
|
9
|
+
initialize: vi.fn().mockResolvedValue(undefined),
|
|
10
|
+
parse: vi.fn(() => ({
|
|
11
|
+
imports: [
|
|
12
|
+
{ source: 'os', specifiers: ['path'], isRelative: false },
|
|
13
|
+
{ source: '.utils', specifiers: ['helper'], isRelative: true },
|
|
14
|
+
],
|
|
15
|
+
exports: [
|
|
16
|
+
{ name: 'MyClass', type: 'class' },
|
|
17
|
+
{ name: 'my_function', type: 'function' },
|
|
18
|
+
],
|
|
19
|
+
})),
|
|
20
|
+
language: 'python',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}),
|
|
25
|
+
estimateTokens: vi.fn((code: string) => Math.ceil(code.length / 4)),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
// Mock fs
|
|
29
|
+
vi.mock('fs', () => ({
|
|
30
|
+
promises: {
|
|
31
|
+
readFile: vi.fn(),
|
|
32
|
+
},
|
|
33
|
+
existsSync: vi.fn(),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
describe('python-context', () => {
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
vi.clearAllMocks();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('analyzePythonContext', () => {
|
|
42
|
+
it('should return empty array when parser is not available', async () => {
|
|
43
|
+
const { getParser } = await import('@aiready/core');
|
|
44
|
+
vi.mocked(getParser).mockResolvedValueOnce(null);
|
|
45
|
+
|
|
46
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
47
|
+
|
|
48
|
+
const results = await pythonContext.analyzePythonContext([], '/test');
|
|
49
|
+
|
|
50
|
+
expect(results).toEqual([]);
|
|
51
|
+
consoleSpy.mockRestore();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should filter for Python files only', async () => {
|
|
55
|
+
const files = ['src/file1.ts', 'src/file2.py', 'src/file3.js'];
|
|
56
|
+
|
|
57
|
+
const results = await pythonContext.analyzePythonContext(files, '/test');
|
|
58
|
+
|
|
59
|
+
// Should handle gracefully even if files don't exist
|
|
60
|
+
expect(Array.isArray(results)).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should analyze Python files and return metrics', async () => {
|
|
64
|
+
const fs = await import('fs');
|
|
65
|
+
vi.mocked(fs.promises.readFile).mockResolvedValue(`
|
|
66
|
+
import os
|
|
67
|
+
from .utils import helper
|
|
68
|
+
|
|
69
|
+
class MyClass:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
def my_function():
|
|
73
|
+
pass
|
|
74
|
+
`);
|
|
75
|
+
|
|
76
|
+
const files = ['src/test.py'];
|
|
77
|
+
|
|
78
|
+
const results = await pythonContext.analyzePythonContext(files, '/test');
|
|
79
|
+
|
|
80
|
+
// Results may be empty if parser returns null in mock
|
|
81
|
+
expect(Array.isArray(results)).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should handle file read errors gracefully', async () => {
|
|
85
|
+
const fs = await import('fs');
|
|
86
|
+
vi.mocked(fs.promises.readFile).mockRejectedValueOnce(
|
|
87
|
+
new Error('File not found')
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
91
|
+
|
|
92
|
+
const files = ['src/bad.py'];
|
|
93
|
+
const results = await pythonContext.analyzePythonContext(files, '/test');
|
|
94
|
+
|
|
95
|
+
// Should return empty results or partial results
|
|
96
|
+
expect(Array.isArray(results)).toBe(true);
|
|
97
|
+
consoleSpy.mockRestore();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { displayConsoleReport } from '../../report/console-report';
|
|
3
|
+
import type { ContextSummary, ContextAnalysisResult } from '../../types';
|
|
4
|
+
|
|
5
|
+
// Mock chalk to avoid ANSI codes in tests
|
|
6
|
+
vi.mock('chalk', () => ({
|
|
7
|
+
default: {
|
|
8
|
+
bold: (str: string) => str,
|
|
9
|
+
cyan: (str: string) => str,
|
|
10
|
+
green: (str: string) => str,
|
|
11
|
+
red: (str: string) => str,
|
|
12
|
+
yellow: (str: string) => str,
|
|
13
|
+
blue: (str: string) => str,
|
|
14
|
+
white: (str: string) => str,
|
|
15
|
+
dim: (str: string) => str,
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
// Mock the analyzer to provide the results type
|
|
20
|
+
vi.mock('../../analyzer', () => ({
|
|
21
|
+
analyzeContext: vi.fn(),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
describe('displayConsoleReport', () => {
|
|
25
|
+
let consoleLogSpy: any;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
consoleLogSpy.mockRestore();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const createMockSummary = (
|
|
36
|
+
overrides: Partial<ContextSummary> = {}
|
|
37
|
+
): ContextSummary => ({
|
|
38
|
+
totalFiles: 10,
|
|
39
|
+
totalTokens: 50000,
|
|
40
|
+
avgContextBudget: 5000,
|
|
41
|
+
maxContextBudget: 10000,
|
|
42
|
+
avgImportDepth: 3,
|
|
43
|
+
maxImportDepth: 7,
|
|
44
|
+
deepFiles: [{ file: 'src/deep/file.ts', depth: 7 }],
|
|
45
|
+
avgFragmentation: 0.3,
|
|
46
|
+
fragmentedModules: [],
|
|
47
|
+
avgCohesion: 0.7,
|
|
48
|
+
lowCohesionFiles: [],
|
|
49
|
+
criticalIssues: 2,
|
|
50
|
+
majorIssues: 5,
|
|
51
|
+
minorIssues: 3,
|
|
52
|
+
totalPotentialSavings: 10000,
|
|
53
|
+
topExpensiveFiles: [],
|
|
54
|
+
config: {},
|
|
55
|
+
...overrides,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const createMockResults = (): ContextAnalysisResult[] => [
|
|
59
|
+
{
|
|
60
|
+
file: 'src/services/user-service.ts',
|
|
61
|
+
tokenCost: 5000,
|
|
62
|
+
linesOfCode: 500,
|
|
63
|
+
importDepth: 5,
|
|
64
|
+
dependencyCount: 10,
|
|
65
|
+
dependencyList: [],
|
|
66
|
+
circularDeps: [],
|
|
67
|
+
cohesionScore: 0.8,
|
|
68
|
+
domains: ['user'],
|
|
69
|
+
exportCount: 5,
|
|
70
|
+
contextBudget: 15000,
|
|
71
|
+
fragmentationScore: 0.3,
|
|
72
|
+
relatedFiles: [],
|
|
73
|
+
fileClassification: 'service-file',
|
|
74
|
+
severity: 'critical',
|
|
75
|
+
issues: ['High context budget'],
|
|
76
|
+
recommendations: ['Split into smaller modules'],
|
|
77
|
+
potentialSavings: 5000,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
file: 'src/utils/helper.ts',
|
|
81
|
+
tokenCost: 1000,
|
|
82
|
+
linesOfCode: 100,
|
|
83
|
+
importDepth: 2,
|
|
84
|
+
dependencyCount: 3,
|
|
85
|
+
dependencyList: [],
|
|
86
|
+
circularDeps: [],
|
|
87
|
+
cohesionScore: 0.9,
|
|
88
|
+
domains: ['utils'],
|
|
89
|
+
exportCount: 3,
|
|
90
|
+
contextBudget: 3000,
|
|
91
|
+
fragmentationScore: 0.1,
|
|
92
|
+
relatedFiles: [],
|
|
93
|
+
fileClassification: 'utility-module',
|
|
94
|
+
severity: 'info',
|
|
95
|
+
issues: [],
|
|
96
|
+
recommendations: [],
|
|
97
|
+
potentialSavings: 0,
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
it('should display summary with all metrics', () => {
|
|
102
|
+
const summary = createMockSummary();
|
|
103
|
+
const results = createMockResults();
|
|
104
|
+
|
|
105
|
+
displayConsoleReport(summary, results);
|
|
106
|
+
|
|
107
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
108
|
+
expect.stringContaining('Context Analysis Summary')
|
|
109
|
+
);
|
|
110
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
111
|
+
expect.stringContaining('Total Files:')
|
|
112
|
+
);
|
|
113
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
114
|
+
expect.stringContaining('Total Tokens:')
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should display issues when present', () => {
|
|
119
|
+
const summary = createMockSummary({
|
|
120
|
+
criticalIssues: 2,
|
|
121
|
+
majorIssues: 5,
|
|
122
|
+
minorIssues: 3,
|
|
123
|
+
});
|
|
124
|
+
const results = createMockResults();
|
|
125
|
+
|
|
126
|
+
displayConsoleReport(summary, results);
|
|
127
|
+
|
|
128
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
129
|
+
expect.stringContaining('Issues Detected')
|
|
130
|
+
);
|
|
131
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
132
|
+
expect.stringContaining('Critical:')
|
|
133
|
+
);
|
|
134
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
135
|
+
expect.stringContaining('Major:')
|
|
136
|
+
);
|
|
137
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
138
|
+
expect.stringContaining('Minor:')
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should display no issues message when all counts are zero', () => {
|
|
143
|
+
const summary = createMockSummary({
|
|
144
|
+
criticalIssues: 0,
|
|
145
|
+
majorIssues: 0,
|
|
146
|
+
minorIssues: 0,
|
|
147
|
+
});
|
|
148
|
+
const results: ContextAnalysisResult[] = [];
|
|
149
|
+
|
|
150
|
+
displayConsoleReport(summary, results);
|
|
151
|
+
|
|
152
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
153
|
+
expect.stringContaining('No significant context issues detected')
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should display fragmented modules when present', () => {
|
|
158
|
+
const summary = createMockSummary({
|
|
159
|
+
fragmentedModules: [
|
|
160
|
+
{
|
|
161
|
+
domain: 'src/features',
|
|
162
|
+
files: [
|
|
163
|
+
'src/features/a.ts',
|
|
164
|
+
'src/features/b.ts',
|
|
165
|
+
'src/features/c.ts',
|
|
166
|
+
],
|
|
167
|
+
totalTokens: 3000,
|
|
168
|
+
fragmentationScore: 0.75,
|
|
169
|
+
avgCohesion: 0.4,
|
|
170
|
+
suggestedStructure: {
|
|
171
|
+
targetFiles: 2,
|
|
172
|
+
consolidationPlan: ['Consolidate into fewer modules'],
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
});
|
|
177
|
+
const results: ContextAnalysisResult[] = [];
|
|
178
|
+
|
|
179
|
+
displayConsoleReport(summary, results);
|
|
180
|
+
|
|
181
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
182
|
+
expect.stringContaining('Fragmented Modules')
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should display top expensive files when present', () => {
|
|
187
|
+
const summary = createMockSummary({
|
|
188
|
+
topExpensiveFiles: [
|
|
189
|
+
{
|
|
190
|
+
file: 'src/services/user-service.ts',
|
|
191
|
+
contextBudget: 15000,
|
|
192
|
+
severity: 'critical',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
file: 'src/services/order-service.ts',
|
|
196
|
+
contextBudget: 12000,
|
|
197
|
+
severity: 'major',
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
});
|
|
201
|
+
const results: ContextAnalysisResult[] = [];
|
|
202
|
+
|
|
203
|
+
displayConsoleReport(summary, results);
|
|
204
|
+
|
|
205
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
206
|
+
expect.stringContaining('Most Expensive Files')
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should display recommendations for critical and major issues', () => {
|
|
211
|
+
const summary = createMockSummary({
|
|
212
|
+
criticalIssues: 1,
|
|
213
|
+
majorIssues: 1,
|
|
214
|
+
minorIssues: 0,
|
|
215
|
+
});
|
|
216
|
+
const results: ContextAnalysisResult[] = [
|
|
217
|
+
{
|
|
218
|
+
file: 'src/bad/file.ts',
|
|
219
|
+
tokenCost: 10000,
|
|
220
|
+
linesOfCode: 1000,
|
|
221
|
+
importDepth: 8,
|
|
222
|
+
dependencyCount: 20,
|
|
223
|
+
dependencyList: [],
|
|
224
|
+
circularDeps: [],
|
|
225
|
+
cohesionScore: 0.2,
|
|
226
|
+
domains: [],
|
|
227
|
+
exportCount: 20,
|
|
228
|
+
contextBudget: 30000,
|
|
229
|
+
fragmentationScore: 0.8,
|
|
230
|
+
relatedFiles: [],
|
|
231
|
+
fileClassification: 'mixed-concerns',
|
|
232
|
+
severity: 'critical',
|
|
233
|
+
issues: ['Very high context budget', 'Deep import chain'],
|
|
234
|
+
recommendations: ['Split into multiple files', 'Reduce dependencies'],
|
|
235
|
+
potentialSavings: 15000,
|
|
236
|
+
},
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
displayConsoleReport(summary, results);
|
|
240
|
+
|
|
241
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
242
|
+
expect.stringContaining('Recommendations')
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should respect maxResults parameter', () => {
|
|
247
|
+
const summary = createMockSummary({
|
|
248
|
+
topExpensiveFiles: [
|
|
249
|
+
{ file: 'src/file1.ts', contextBudget: 15000, severity: 'critical' },
|
|
250
|
+
{ file: 'src/file2.ts', contextBudget: 12000, severity: 'critical' },
|
|
251
|
+
{ file: 'src/file3.ts', contextBudget: 10000, severity: 'major' },
|
|
252
|
+
{ file: 'src/file4.ts', contextBudget: 8000, severity: 'major' },
|
|
253
|
+
],
|
|
254
|
+
fragmentedModules: [
|
|
255
|
+
{
|
|
256
|
+
domain: 'src/domain1',
|
|
257
|
+
files: ['a.ts', 'b.ts', 'c.ts', 'd.ts'],
|
|
258
|
+
totalTokens: 2000,
|
|
259
|
+
fragmentationScore: 0.6,
|
|
260
|
+
avgCohesion: 0.5,
|
|
261
|
+
suggestedStructure: { targetFiles: 2, consolidationPlan: [] },
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
domain: 'src/domain2',
|
|
265
|
+
files: ['e.ts', 'f.ts'],
|
|
266
|
+
totalTokens: 1000,
|
|
267
|
+
fragmentationScore: 0.5,
|
|
268
|
+
avgCohesion: 0.6,
|
|
269
|
+
suggestedStructure: { targetFiles: 1, consolidationPlan: [] },
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
});
|
|
273
|
+
const results: ContextAnalysisResult[] = [];
|
|
274
|
+
|
|
275
|
+
displayConsoleReport(summary, results, 2);
|
|
276
|
+
|
|
277
|
+
// With maxResults=2, should only show top 2 items
|
|
278
|
+
// The function uses slice(0, maxResults) internally
|
|
279
|
+
expect(consoleLogSpy).toHaveBeenCalled();
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should display footer with links', () => {
|
|
283
|
+
const summary = createMockSummary();
|
|
284
|
+
const results: ContextAnalysisResult[] = [];
|
|
285
|
+
|
|
286
|
+
displayConsoleReport(summary, results);
|
|
287
|
+
|
|
288
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
289
|
+
expect.stringContaining('github.com')
|
|
290
|
+
);
|
|
291
|
+
});
|
|
292
|
+
});
|