@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.
Files changed (61) hide show
  1. package/.turbo/turbo-build.log +26 -25
  2. package/.turbo/turbo-lint.log +5 -5
  3. package/.turbo/turbo-test.log +104 -41
  4. package/coverage/clover.xml +2615 -1242
  5. package/coverage/coverage-final.json +30 -13
  6. package/coverage/dist/chunk-64U3PNO3.mjs.html +367 -0
  7. package/coverage/dist/chunk-J3MUOWHC.mjs.html +5326 -0
  8. package/coverage/dist/index.html +146 -0
  9. package/coverage/{classifier.ts.html → dist/index.mjs.html} +537 -912
  10. package/coverage/index.html +84 -189
  11. package/coverage/src/analyzer.ts.html +88 -0
  12. package/coverage/src/analyzers/index.html +116 -0
  13. package/coverage/src/analyzers/python-context.ts.html +910 -0
  14. package/coverage/{ast-utils.ts.html → src/ast-utils.ts.html} +84 -54
  15. package/coverage/src/classifier.ts.html +892 -0
  16. package/coverage/src/classify/classification-patterns.ts.html +307 -0
  17. package/coverage/src/classify/file-classifiers.ts.html +973 -0
  18. package/coverage/src/classify/index.html +131 -0
  19. package/coverage/{cluster-detector.ts.html → src/cluster-detector.ts.html} +154 -91
  20. package/coverage/{defaults.ts.html → src/defaults.ts.html} +74 -65
  21. package/coverage/{graph-builder.ts.html → src/graph-builder.ts.html} +268 -229
  22. package/coverage/src/index.html +341 -0
  23. package/coverage/{index.ts.html → src/index.ts.html} +70 -13
  24. package/coverage/{scoring.ts.html → src/issue-analyzer.ts.html} +201 -261
  25. package/coverage/src/mapper.ts.html +439 -0
  26. package/coverage/{metrics.ts.html → src/metrics.ts.html} +201 -132
  27. package/coverage/src/orchestrator.ts.html +493 -0
  28. package/coverage/{provider.ts.html → src/provider.ts.html} +21 -21
  29. package/coverage/{remediation.ts.html → src/remediation.ts.html} +112 -52
  30. package/coverage/src/report/console-report.ts.html +415 -0
  31. package/coverage/src/report/html-report.ts.html +361 -0
  32. package/coverage/src/report/index.html +146 -0
  33. package/coverage/src/report/interactive-setup.ts.html +373 -0
  34. package/coverage/src/scoring.ts.html +895 -0
  35. package/coverage/src/semantic/co-usage.ts.html +340 -0
  36. package/coverage/src/semantic/consolidation.ts.html +223 -0
  37. package/coverage/src/semantic/domain-inference.ts.html +859 -0
  38. package/coverage/src/semantic/index.html +161 -0
  39. package/coverage/src/semantic/type-graph.ts.html +163 -0
  40. package/coverage/{summary.ts.html → src/summary.ts.html} +155 -275
  41. package/coverage/{types.ts.html → src/types.ts.html} +133 -31
  42. package/coverage/src/utils/dependency-graph-utils.ts.html +463 -0
  43. package/coverage/src/utils/index.html +131 -0
  44. package/coverage/src/utils/string-utils.ts.html +148 -0
  45. package/dist/chunk-J3MUOWHC.mjs +1747 -0
  46. package/dist/cli.js +59 -171
  47. package/dist/cli.mjs +1 -1
  48. package/dist/index.js +55 -167
  49. package/dist/index.mjs +1 -1
  50. package/package.json +2 -2
  51. package/src/__tests__/consolidation.test.ts +247 -0
  52. package/src/__tests__/defaults.test.ts +121 -0
  53. package/src/__tests__/domain-inference.test.ts +420 -0
  54. package/src/__tests__/issue-analyzer.test.ts +155 -0
  55. package/src/__tests__/orchestrator.test.ts +143 -0
  56. package/src/__tests__/python-context.test.ts +98 -0
  57. package/src/__tests__/report/console-report.test.ts +292 -0
  58. package/src/__tests__/report/html-report.test.ts +232 -0
  59. package/src/report/html-report.ts +58 -174
  60. package/coverage/analyzer.ts.html +0 -1369
  61. 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
+ });