@aiready/context-analyzer 0.21.22 → 0.21.23
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 +104 -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-J3MUOWHC.mjs +1747 -0
- package/dist/cli.js +59 -171
- package/dist/cli.mjs +1 -1
- package/dist/index.js +55 -167
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
- 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__/issue-analyzer.test.ts +155 -0
- package/src/__tests__/orchestrator.test.ts +143 -0
- package/src/__tests__/python-context.test.ts +98 -0
- package/src/__tests__/report/console-report.test.ts +292 -0
- package/src/__tests__/report/html-report.test.ts +232 -0
- package/src/report/html-report.ts +58 -174
- package/coverage/analyzer.ts.html +0 -1369
- package/coverage/semantic-analysis.ts.html +0 -1201
|
@@ -0,0 +1,98 @@
|
|
|
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((filename: string) => {
|
|
7
|
+
if (filename.endsWith('.py')) {
|
|
8
|
+
return {
|
|
9
|
+
parse: vi.fn(() => ({
|
|
10
|
+
imports: [
|
|
11
|
+
{ source: 'os', specifiers: ['path'], isRelative: false },
|
|
12
|
+
{ source: '.utils', specifiers: ['helper'], isRelative: true },
|
|
13
|
+
],
|
|
14
|
+
exports: [
|
|
15
|
+
{ name: 'MyClass', type: 'class' },
|
|
16
|
+
{ name: 'my_function', type: 'function' },
|
|
17
|
+
],
|
|
18
|
+
})),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}),
|
|
23
|
+
estimateTokens: vi.fn((code: string) => Math.ceil(code.length / 4)),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
// Mock fs
|
|
27
|
+
vi.mock('fs', () => ({
|
|
28
|
+
promises: {
|
|
29
|
+
readFile: vi.fn(),
|
|
30
|
+
},
|
|
31
|
+
existsSync: vi.fn(),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
describe('python-context', () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
vi.clearAllMocks();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('analyzePythonContext', () => {
|
|
40
|
+
it('should return empty array when parser is not available', async () => {
|
|
41
|
+
const { getParser } = await import('@aiready/core');
|
|
42
|
+
vi.mocked(getParser).mockReturnValueOnce(null);
|
|
43
|
+
|
|
44
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
45
|
+
|
|
46
|
+
const results = await pythonContext.analyzePythonContext([], '/test');
|
|
47
|
+
|
|
48
|
+
expect(results).toEqual([]);
|
|
49
|
+
consoleSpy.mockRestore();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should filter for Python files only', async () => {
|
|
53
|
+
const files = ['src/file1.ts', 'src/file2.py', 'src/file3.js'];
|
|
54
|
+
|
|
55
|
+
const results = await pythonContext.analyzePythonContext(files, '/test');
|
|
56
|
+
|
|
57
|
+
// Should handle gracefully even if files don't exist
|
|
58
|
+
expect(Array.isArray(results)).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should analyze Python files and return metrics', async () => {
|
|
62
|
+
const fs = await import('fs');
|
|
63
|
+
vi.mocked(fs.promises.readFile).mockResolvedValue(`
|
|
64
|
+
import os
|
|
65
|
+
from .utils import helper
|
|
66
|
+
|
|
67
|
+
class MyClass:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
def my_function():
|
|
71
|
+
pass
|
|
72
|
+
`);
|
|
73
|
+
|
|
74
|
+
const files = ['src/test.py'];
|
|
75
|
+
|
|
76
|
+
const results = await pythonContext.analyzePythonContext(files, '/test');
|
|
77
|
+
|
|
78
|
+
// Results may be empty if parser returns null in mock
|
|
79
|
+
expect(Array.isArray(results)).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should handle file read errors gracefully', async () => {
|
|
83
|
+
const fs = await import('fs');
|
|
84
|
+
vi.mocked(fs.promises.readFile).mockRejectedValueOnce(
|
|
85
|
+
new Error('File not found')
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
89
|
+
|
|
90
|
+
const files = ['src/bad.py'];
|
|
91
|
+
const results = await pythonContext.analyzePythonContext(files, '/test');
|
|
92
|
+
|
|
93
|
+
// Should return empty results or partial results
|
|
94
|
+
expect(Array.isArray(results)).toBe(true);
|
|
95
|
+
consoleSpy.mockRestore();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { generateHTMLReport } from '../../report/html-report';
|
|
3
|
+
import type { ContextSummary, ContextAnalysisResult } from '../../types';
|
|
4
|
+
|
|
5
|
+
// Mock @aiready/core
|
|
6
|
+
vi.mock('@aiready/core', () => ({
|
|
7
|
+
generateReportHead: (title: string) => `<head><title>${title}</title></head>`,
|
|
8
|
+
generateStatCards: (stats: any[]) =>
|
|
9
|
+
`<div class="stats">${stats.map((s) => s.label).join(', ')}</div>`,
|
|
10
|
+
generateTable: (options: { headers: string[]; rows: string[][] }) =>
|
|
11
|
+
`<table><thead><tr>${options.headers.map((h) => `<th>${h}</th>`).join('')}</thead><tbody>${options.rows.map((r) => `<tr>${r.map((c) => `<td>${c}</td>`).join('')}</tr>`).join('')}</tbody></table>`,
|
|
12
|
+
generateReportFooter: (options: any) =>
|
|
13
|
+
`<footer>${options.title} - ${options.packageUrl}</footer>`,
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
describe('generateHTMLReport', () => {
|
|
17
|
+
const createMockSummary = (
|
|
18
|
+
overrides: Partial<ContextSummary> = {}
|
|
19
|
+
): ContextSummary => ({
|
|
20
|
+
totalFiles: 10,
|
|
21
|
+
totalTokens: 50000,
|
|
22
|
+
avgContextBudget: 5000,
|
|
23
|
+
maxContextBudget: 10000,
|
|
24
|
+
avgImportDepth: 3,
|
|
25
|
+
maxImportDepth: 7,
|
|
26
|
+
deepFiles: [],
|
|
27
|
+
avgFragmentation: 0.3,
|
|
28
|
+
fragmentedModules: [],
|
|
29
|
+
avgCohesion: 0.7,
|
|
30
|
+
lowCohesionFiles: [],
|
|
31
|
+
criticalIssues: 2,
|
|
32
|
+
majorIssues: 5,
|
|
33
|
+
minorIssues: 3,
|
|
34
|
+
totalPotentialSavings: 10000,
|
|
35
|
+
topExpensiveFiles: [],
|
|
36
|
+
config: {},
|
|
37
|
+
...overrides,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const createMockResults = (): ContextAnalysisResult[] => [
|
|
41
|
+
{
|
|
42
|
+
file: 'src/services/user-service.ts',
|
|
43
|
+
tokenCost: 5000,
|
|
44
|
+
linesOfCode: 500,
|
|
45
|
+
importDepth: 5,
|
|
46
|
+
dependencyCount: 10,
|
|
47
|
+
dependencyList: [],
|
|
48
|
+
circularDeps: [],
|
|
49
|
+
cohesionScore: 0.8,
|
|
50
|
+
domains: ['user'],
|
|
51
|
+
exportCount: 5,
|
|
52
|
+
contextBudget: 15000,
|
|
53
|
+
fragmentationScore: 0.3,
|
|
54
|
+
relatedFiles: [],
|
|
55
|
+
fileClassification: 'service-file',
|
|
56
|
+
severity: 'critical',
|
|
57
|
+
issues: ['High context budget'],
|
|
58
|
+
recommendations: ['Split into smaller modules'],
|
|
59
|
+
potentialSavings: 5000,
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
it('should generate HTML report with all sections', () => {
|
|
64
|
+
const summary = createMockSummary({
|
|
65
|
+
criticalIssues: 2,
|
|
66
|
+
majorIssues: 1,
|
|
67
|
+
minorIssues: 1,
|
|
68
|
+
});
|
|
69
|
+
const results = createMockResults();
|
|
70
|
+
|
|
71
|
+
const html = generateHTMLReport(summary, results);
|
|
72
|
+
|
|
73
|
+
expect(html).toContain('AIReady Context Analysis Report');
|
|
74
|
+
expect(html).toContain('head');
|
|
75
|
+
expect(html).toContain('body');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should include stats cards', () => {
|
|
79
|
+
const summary = createMockSummary();
|
|
80
|
+
const results: ContextAnalysisResult[] = [];
|
|
81
|
+
|
|
82
|
+
const html = generateHTMLReport(summary, results);
|
|
83
|
+
|
|
84
|
+
expect(html).toContain('Files Analyzed');
|
|
85
|
+
expect(html).toContain('Total Tokens');
|
|
86
|
+
expect(html).toContain('Avg Context Budget');
|
|
87
|
+
expect(html).toContain('Total Issues');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should include issues summary when issues exist', () => {
|
|
91
|
+
const summary = createMockSummary({
|
|
92
|
+
criticalIssues: 2,
|
|
93
|
+
majorIssues: 3,
|
|
94
|
+
minorIssues: 1,
|
|
95
|
+
totalPotentialSavings: 5000,
|
|
96
|
+
});
|
|
97
|
+
const results: ContextAnalysisResult[] = [];
|
|
98
|
+
|
|
99
|
+
const html = generateHTMLReport(summary, results);
|
|
100
|
+
|
|
101
|
+
expect(html).toContain('Issues Summary');
|
|
102
|
+
expect(html).toContain('Critical:');
|
|
103
|
+
expect(html).toContain('Major:');
|
|
104
|
+
expect(html).toContain('Minor:');
|
|
105
|
+
expect(html).toContain('Potential Savings');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should not include issues section when no issues', () => {
|
|
109
|
+
const summary = createMockSummary({
|
|
110
|
+
criticalIssues: 0,
|
|
111
|
+
majorIssues: 0,
|
|
112
|
+
minorIssues: 0,
|
|
113
|
+
});
|
|
114
|
+
const results: ContextAnalysisResult[] = [];
|
|
115
|
+
|
|
116
|
+
const html = generateHTMLReport(summary, results);
|
|
117
|
+
|
|
118
|
+
expect(html).not.toContain('Issues Summary');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should include fragmented modules when present', () => {
|
|
122
|
+
const summary = createMockSummary({
|
|
123
|
+
fragmentedModules: [
|
|
124
|
+
{
|
|
125
|
+
domain: 'src/features',
|
|
126
|
+
files: ['a.ts', 'b.ts', 'c.ts'],
|
|
127
|
+
totalTokens: 3000,
|
|
128
|
+
fragmentationScore: 0.75,
|
|
129
|
+
avgCohesion: 0.4,
|
|
130
|
+
suggestedStructure: {
|
|
131
|
+
targetFiles: 2,
|
|
132
|
+
consolidationPlan: ['Consolidate into fewer modules'],
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
});
|
|
137
|
+
const results: ContextAnalysisResult[] = [];
|
|
138
|
+
|
|
139
|
+
const html = generateHTMLReport(summary, results);
|
|
140
|
+
|
|
141
|
+
expect(html).toContain('Fragmented Modules');
|
|
142
|
+
expect(html).toContain('src/features');
|
|
143
|
+
expect(html).toContain('Domain');
|
|
144
|
+
expect(html).toContain('Files');
|
|
145
|
+
expect(html).toContain('Fragmentation');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should not include fragmented modules when none exist', () => {
|
|
149
|
+
const summary = createMockSummary({
|
|
150
|
+
fragmentedModules: [],
|
|
151
|
+
});
|
|
152
|
+
const results: ContextAnalysisResult[] = [];
|
|
153
|
+
|
|
154
|
+
const html = generateHTMLReport(summary, results);
|
|
155
|
+
|
|
156
|
+
expect(html).not.toContain('Fragmented Modules');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should include expensive files when present', () => {
|
|
160
|
+
const summary = createMockSummary({
|
|
161
|
+
topExpensiveFiles: [
|
|
162
|
+
{
|
|
163
|
+
file: 'src/services/user-service.ts',
|
|
164
|
+
contextBudget: 15000,
|
|
165
|
+
severity: 'critical',
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
file: 'src/services/order-service.ts',
|
|
169
|
+
contextBudget: 12000,
|
|
170
|
+
severity: 'major',
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
});
|
|
174
|
+
const results: ContextAnalysisResult[] = [];
|
|
175
|
+
|
|
176
|
+
const html = generateHTMLReport(summary, results);
|
|
177
|
+
|
|
178
|
+
expect(html).toContain('Most Expensive Files');
|
|
179
|
+
expect(html).toContain('src/services/user-service.ts');
|
|
180
|
+
expect(html).toContain('Context Budget');
|
|
181
|
+
expect(html).toContain('Severity');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should not include expensive files when none exist', () => {
|
|
185
|
+
const summary = createMockSummary({
|
|
186
|
+
topExpensiveFiles: [],
|
|
187
|
+
});
|
|
188
|
+
const results: ContextAnalysisResult[] = [];
|
|
189
|
+
|
|
190
|
+
const html = generateHTMLReport(summary, results);
|
|
191
|
+
|
|
192
|
+
expect(html).not.toContain('Most Expensive Files');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should include footer with package info', () => {
|
|
196
|
+
const summary = createMockSummary();
|
|
197
|
+
const results: ContextAnalysisResult[] = [];
|
|
198
|
+
|
|
199
|
+
const html = generateHTMLReport(summary, results);
|
|
200
|
+
|
|
201
|
+
expect(html).toContain('<footer>');
|
|
202
|
+
expect(html).toContain('context-analyzer');
|
|
203
|
+
expect(html).toContain('github.com');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should include token values in stats', () => {
|
|
207
|
+
const summary = createMockSummary({
|
|
208
|
+
totalTokens: 50000,
|
|
209
|
+
avgContextBudget: 5000,
|
|
210
|
+
});
|
|
211
|
+
const results: ContextAnalysisResult[] = [];
|
|
212
|
+
|
|
213
|
+
const html = generateHTMLReport(summary, results);
|
|
214
|
+
|
|
215
|
+
// Should include stats section
|
|
216
|
+
expect(html).toContain('Total Tokens');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should show issues when present', () => {
|
|
220
|
+
const summary = createMockSummary({
|
|
221
|
+
criticalIssues: 1,
|
|
222
|
+
majorIssues: 0,
|
|
223
|
+
minorIssues: 0,
|
|
224
|
+
});
|
|
225
|
+
const results: ContextAnalysisResult[] = [];
|
|
226
|
+
|
|
227
|
+
const html = generateHTMLReport(summary, results);
|
|
228
|
+
|
|
229
|
+
// Should show issues section
|
|
230
|
+
expect(html).toContain('Issues Summary');
|
|
231
|
+
});
|
|
232
|
+
});
|