@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.
Files changed (78) hide show
  1. package/.turbo/turbo-build.log +26 -25
  2. package/.turbo/turbo-lint.log +5 -5
  3. package/.turbo/turbo-test.log +91 -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-AMPK6SWS.mjs +1754 -0
  46. package/dist/chunk-BHCRDEE4.mjs +1745 -0
  47. package/dist/chunk-IKRP7ECY.mjs +1754 -0
  48. package/dist/chunk-J3MUOWHC.mjs +1747 -0
  49. package/dist/chunk-TWWPY7FD.mjs +1754 -0
  50. package/dist/chunk-Z5WY6A4P.mjs +1754 -0
  51. package/dist/cli.js +77 -185
  52. package/dist/cli.mjs +1 -1
  53. package/dist/index.d.mts +1 -1
  54. package/dist/index.d.ts +1 -1
  55. package/dist/index.js +73 -181
  56. package/dist/index.mjs +1 -1
  57. package/dist/python-context-BWDC4E5Z.mjs +162 -0
  58. package/package.json +3 -3
  59. package/src/__tests__/analyzer.test.ts +14 -14
  60. package/src/__tests__/auto-detection.test.ts +16 -16
  61. package/src/__tests__/consolidation.test.ts +247 -0
  62. package/src/__tests__/defaults.test.ts +121 -0
  63. package/src/__tests__/domain-inference.test.ts +420 -0
  64. package/src/__tests__/fragmentation-coupling.test.ts +4 -4
  65. package/src/__tests__/issue-analyzer.test.ts +155 -0
  66. package/src/__tests__/orchestrator.test.ts +143 -0
  67. package/src/__tests__/python-context.test.ts +100 -0
  68. package/src/__tests__/report/console-report.test.ts +292 -0
  69. package/src/__tests__/report/html-report.test.ts +243 -0
  70. package/src/__tests__/scoring.test.ts +17 -11
  71. package/src/analyzers/python-context.ts +2 -2
  72. package/src/ast-utils.ts +3 -3
  73. package/src/graph-builder.ts +4 -4
  74. package/src/mapper.ts +6 -0
  75. package/src/orchestrator.ts +1 -1
  76. package/src/report/html-report.ts +73 -181
  77. package/coverage/analyzer.ts.html +0 -1369
  78. package/coverage/semantic-analysis.ts.html +0 -1201
@@ -0,0 +1,243 @@
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
+ generateReportHero: (title: string, subtitle?: string) =>
15
+ `<div class="hero"><h1>${title}</h1><p>${subtitle}</p></div>`,
16
+ generateIssueSummary: (
17
+ critical: number,
18
+ major: number,
19
+ minor: number,
20
+ savings?: number
21
+ ) =>
22
+ `<div class="issue-summary">Issues: Critical:${critical}, Major:${major}, Minor:${minor}. Savings: ${savings}</div>`,
23
+ wrapInCard: (content: string, title?: string) =>
24
+ `<div class="card"><h2>${title}</h2>${content}</div>`,
25
+ }));
26
+
27
+ describe('generateHTMLReport', () => {
28
+ const createMockSummary = (
29
+ overrides: Partial<ContextSummary> = {}
30
+ ): ContextSummary => ({
31
+ totalFiles: 10,
32
+ totalTokens: 50000,
33
+ avgContextBudget: 5000,
34
+ maxContextBudget: 10000,
35
+ avgImportDepth: 3,
36
+ maxImportDepth: 7,
37
+ deepFiles: [],
38
+ avgFragmentation: 0.3,
39
+ fragmentedModules: [],
40
+ avgCohesion: 0.7,
41
+ lowCohesionFiles: [],
42
+ criticalIssues: 2,
43
+ majorIssues: 5,
44
+ minorIssues: 3,
45
+ totalPotentialSavings: 10000,
46
+ topExpensiveFiles: [],
47
+ config: {},
48
+ ...overrides,
49
+ });
50
+
51
+ const createMockResults = (): ContextAnalysisResult[] => [
52
+ {
53
+ file: 'src/services/user-service.ts',
54
+ tokenCost: 5000,
55
+ linesOfCode: 500,
56
+ importDepth: 5,
57
+ dependencyCount: 10,
58
+ dependencyList: [],
59
+ circularDeps: [],
60
+ cohesionScore: 0.8,
61
+ domains: ['user'],
62
+ exportCount: 5,
63
+ contextBudget: 15000,
64
+ fragmentationScore: 0.3,
65
+ relatedFiles: [],
66
+ fileClassification: 'service-file',
67
+ severity: 'critical',
68
+ issues: ['High context budget'],
69
+ recommendations: ['Split into smaller modules'],
70
+ potentialSavings: 5000,
71
+ },
72
+ ];
73
+
74
+ it('should generate HTML report with all sections', () => {
75
+ const summary = createMockSummary({
76
+ criticalIssues: 2,
77
+ majorIssues: 1,
78
+ minorIssues: 1,
79
+ });
80
+ const results = createMockResults();
81
+
82
+ const html = generateHTMLReport(summary, results);
83
+
84
+ expect(html).toContain('AIReady Context Analysis Report');
85
+ expect(html).toContain('head');
86
+ expect(html).toContain('body');
87
+ });
88
+
89
+ it('should include stats cards', () => {
90
+ const summary = createMockSummary();
91
+ const results: ContextAnalysisResult[] = [];
92
+
93
+ const html = generateHTMLReport(summary, results);
94
+
95
+ expect(html).toContain('Files Analyzed');
96
+ expect(html).toContain('Total Tokens');
97
+ expect(html).toContain('Avg Context Budget');
98
+ expect(html).toContain('Total Issues');
99
+ });
100
+
101
+ it('should include issues summary when issues exist', () => {
102
+ const summary = createMockSummary({
103
+ criticalIssues: 2,
104
+ majorIssues: 3,
105
+ minorIssues: 1,
106
+ totalPotentialSavings: 5000,
107
+ });
108
+ const results: ContextAnalysisResult[] = [];
109
+
110
+ const html = generateHTMLReport(summary, results);
111
+
112
+ expect(html).toContain('Issues:');
113
+ expect(html).toContain('Critical:');
114
+ expect(html).toContain('Major:');
115
+ expect(html).toContain('Minor:');
116
+ expect(html).toContain('Savings');
117
+ });
118
+
119
+ it('should not include issues section when no issues', () => {
120
+ const summary = createMockSummary({
121
+ criticalIssues: 0,
122
+ majorIssues: 0,
123
+ minorIssues: 0,
124
+ });
125
+ const results: ContextAnalysisResult[] = [];
126
+
127
+ const html = generateHTMLReport(summary, results);
128
+
129
+ expect(html).not.toContain('Issues Summary');
130
+ });
131
+
132
+ it('should include fragmented modules when present', () => {
133
+ const summary = createMockSummary({
134
+ fragmentedModules: [
135
+ {
136
+ domain: 'src/features',
137
+ files: ['a.ts', 'b.ts', 'c.ts'],
138
+ totalTokens: 3000,
139
+ fragmentationScore: 0.75,
140
+ avgCohesion: 0.4,
141
+ suggestedStructure: {
142
+ targetFiles: 2,
143
+ consolidationPlan: ['Consolidate into fewer modules'],
144
+ },
145
+ },
146
+ ],
147
+ });
148
+ const results: ContextAnalysisResult[] = [];
149
+
150
+ const html = generateHTMLReport(summary, results);
151
+
152
+ expect(html).toContain('Fragmented Modules');
153
+ expect(html).toContain('src/features');
154
+ expect(html).toContain('Domain');
155
+ expect(html).toContain('Files');
156
+ expect(html).toContain('Fragmentation');
157
+ });
158
+
159
+ it('should not include fragmented modules when none exist', () => {
160
+ const summary = createMockSummary({
161
+ fragmentedModules: [],
162
+ });
163
+ const results: ContextAnalysisResult[] = [];
164
+
165
+ const html = generateHTMLReport(summary, results);
166
+
167
+ expect(html).not.toContain('Fragmented Modules');
168
+ });
169
+
170
+ it('should include expensive files when present', () => {
171
+ const summary = createMockSummary({
172
+ topExpensiveFiles: [
173
+ {
174
+ file: 'src/services/user-service.ts',
175
+ contextBudget: 15000,
176
+ severity: 'critical',
177
+ },
178
+ {
179
+ file: 'src/services/order-service.ts',
180
+ contextBudget: 12000,
181
+ severity: 'major',
182
+ },
183
+ ],
184
+ });
185
+ const results: ContextAnalysisResult[] = [];
186
+
187
+ const html = generateHTMLReport(summary, results);
188
+
189
+ expect(html).toContain('Most Expensive Files');
190
+ expect(html).toContain('src/services/user-service.ts');
191
+ expect(html).toContain('Context Budget');
192
+ expect(html).toContain('Severity');
193
+ });
194
+
195
+ it('should not include expensive files when none exist', () => {
196
+ const summary = createMockSummary({
197
+ topExpensiveFiles: [],
198
+ });
199
+ const results: ContextAnalysisResult[] = [];
200
+
201
+ const html = generateHTMLReport(summary, results);
202
+
203
+ expect(html).not.toContain('Most Expensive Files');
204
+ });
205
+
206
+ it('should include footer with package info', () => {
207
+ const summary = createMockSummary();
208
+ const results: ContextAnalysisResult[] = [];
209
+
210
+ const html = generateHTMLReport(summary, results);
211
+
212
+ expect(html).toContain('<footer>');
213
+ expect(html).toContain('context-analyzer');
214
+ expect(html).toContain('github.com');
215
+ });
216
+
217
+ it('should include token values in stats', () => {
218
+ const summary = createMockSummary({
219
+ totalTokens: 50000,
220
+ avgContextBudget: 5000,
221
+ });
222
+ const results: ContextAnalysisResult[] = [];
223
+
224
+ const html = generateHTMLReport(summary, results);
225
+
226
+ // Should include stats section
227
+ expect(html).toContain('Total Tokens');
228
+ });
229
+
230
+ it('should show issues when present', () => {
231
+ const summary = createMockSummary({
232
+ criticalIssues: 1,
233
+ majorIssues: 0,
234
+ minorIssues: 0,
235
+ });
236
+ const results: ContextAnalysisResult[] = [];
237
+
238
+ const html = generateHTMLReport(summary, results);
239
+
240
+ // Should show issues section
241
+ expect(html).toContain('Issues:');
242
+ });
243
+ });
@@ -38,7 +38,7 @@ describe('Context Scoring', () => {
38
38
  const result = calculateContextScore(summary);
39
39
 
40
40
  expect(result.score).toBeLessThan(70);
41
- expect(result.factors.some((f) => f.name === 'Context Budget')).toBe(
41
+ expect(result.factors.some((f: any) => f.name === 'Context Budget')).toBe(
42
42
  true
43
43
  );
44
44
  expect(result.recommendations.length).toBeGreaterThan(0);
@@ -60,9 +60,13 @@ describe('Context Scoring', () => {
60
60
 
61
61
  // With depth=12: depthScore=80, rawScore=100*0.35+80*0.25+100*0.25=80, no bonus (frag=0.2 not <0.2)
62
62
  expect(result.score).toBe(80);
63
- expect(result.factors.some((f) => f.name === 'Import Depth')).toBe(true);
63
+ expect(result.factors.some((f: any) => f.name === 'Import Depth')).toBe(
64
+ true
65
+ );
64
66
  expect(
65
- result.recommendations.some((r) => r.action.includes('import chains'))
67
+ result.recommendations.some((r: any) =>
68
+ r.action.includes('import chains')
69
+ )
66
70
  ).toBe(true);
67
71
  });
68
72
 
@@ -86,7 +90,9 @@ describe('Context Scoring', () => {
86
90
  // Actually frag=0.7 >= 0.2, so no bonus
87
91
  // rawScore = 80, no penalties = 80
88
92
  expect(result.score).toBeLessThan(85); // Adjusted for new calculation
89
- expect(result.factors.some((f) => f.name === 'Fragmentation')).toBe(true);
93
+ expect(result.factors.some((f: any) => f.name === 'Fragmentation')).toBe(
94
+ true
95
+ );
90
96
  });
91
97
 
92
98
  it('should apply critical issue penalties', () => {
@@ -108,9 +114,9 @@ describe('Context Scoring', () => {
108
114
  // criticalPenalty = min(20, 5*3) = min(20,15) = 15
109
115
  // finalScore = 85 - 15 = 70
110
116
  expect(result.score).toBe(70);
111
- expect(result.factors.some((f) => f.name === 'Critical Issues')).toBe(
112
- true
113
- );
117
+ expect(
118
+ result.factors.some((f: any) => f.name === 'Critical Issues')
119
+ ).toBe(true);
114
120
  });
115
121
 
116
122
  it('should handle extreme max budget penalty', () => {
@@ -128,10 +134,10 @@ describe('Context Scoring', () => {
128
134
  const result = calculateContextScore(summary);
129
135
 
130
136
  expect(
131
- result.factors.some((f) => f.name === 'Extreme File Detected')
137
+ result.factors.some((f: any) => f.name === 'Extreme File Detected')
132
138
  ).toBe(true);
133
139
  expect(
134
- result.recommendations.some((r) =>
140
+ result.recommendations.some((r: any) =>
135
141
  r.action.includes('Split large file')
136
142
  )
137
143
  ).toBe(true);
@@ -214,7 +220,7 @@ describe('Context Scoring', () => {
214
220
  // finalScore = 90
215
221
  expect(result.score).toBe(90);
216
222
  expect(
217
- result.factors.some((f) => f.name === 'Well-Organized Codebase')
223
+ result.factors.some((f: any) => f.name === 'Well-Organized Codebase')
218
224
  ).toBe(true);
219
225
  });
220
226
 
@@ -235,7 +241,7 @@ describe('Context Scoring', () => {
235
241
  // No bonus because fragmentation >= 0.2
236
242
  expect(result.score).toBe(85); // 100*0.35 + 100*0.25 + 100*0.25 = 85
237
243
  expect(
238
- result.factors.some((f) => f.name === 'Well-Organized Codebase')
244
+ result.factors.some((f: any) => f.name === 'Well-Organized Codebase')
239
245
  ).toBe(false);
240
246
  });
241
247
 
@@ -51,7 +51,7 @@ export async function analyzePythonContext(
51
51
  rootDir: string
52
52
  ): Promise<PythonContextMetrics[]> {
53
53
  const results: PythonContextMetrics[] = [];
54
- const parser = getParser('dummy.py');
54
+ const parser = await getParser('dummy.py');
55
55
 
56
56
  if (!parser) {
57
57
  console.warn('Python parser not available');
@@ -135,7 +135,7 @@ async function buildPythonDependencyGraph(
135
135
  rootDir: string
136
136
  ): Promise<Map<string, Set<string>>> {
137
137
  const graph = new Map<string, Set<string>>();
138
- const parser = getParser('dummy.py');
138
+ const parser = await getParser('dummy.py');
139
139
 
140
140
  if (!parser) return graph;
141
141
 
package/src/ast-utils.ts CHANGED
@@ -12,14 +12,14 @@ import { inferDomain, extractExports } from './semantic/domain-inference';
12
12
  * @returns Array of high-fidelity export metadata.
13
13
  * @lastUpdated 2026-03-18
14
14
  */
15
- export function extractExportsWithAST(
15
+ export async function extractExportsWithAST(
16
16
  content: string,
17
17
  filePath: string,
18
18
  domainOptions?: { domainKeywords?: string[] },
19
19
  fileImports?: string[]
20
- ): ExportInfo[] {
20
+ ): Promise<ExportInfo[]> {
21
21
  try {
22
- const { exports: astExports } = parseFileExports(content, filePath);
22
+ const { exports: astExports } = await parseFileExports(content, filePath);
23
23
 
24
24
  if (astExports.length === 0 && !isTestFile(filePath)) {
25
25
  // If AST fails to find anything, we still use regex as a last resort
@@ -128,10 +128,10 @@ export function extractDomainKeywordsFromPaths(files: FileContent[]): string[] {
128
128
  * @param options - Optional configuration for domain detection.
129
129
  * @returns Complete dependency graph with nodes, edges, and semantic matrices.
130
130
  */
131
- export function buildDependencyGraph(
131
+ export async function buildDependencyGraph(
132
132
  files: FileContent[],
133
133
  options?: { domainKeywords?: string[] }
134
- ): DependencyGraph {
134
+ ): Promise<DependencyGraph> {
135
135
  const nodes = new Map<string, DependencyNode>();
136
136
  const edges = new Map<string, Set<string>>();
137
137
 
@@ -142,7 +142,7 @@ export function buildDependencyGraph(
142
142
 
143
143
  for (const { file, content } of files) {
144
144
  // 1. Get high-fidelity AST-based imports & exports
145
- const { imports: astImports } = parseFileExports(content, file);
145
+ const { imports: astImports } = await parseFileExports(content, file);
146
146
 
147
147
  // 2. Resolve imports to absolute paths in the graph
148
148
  const resolvedImports = astImports
@@ -152,7 +152,7 @@ export function buildDependencyGraph(
152
152
  const importSources = astImports.map((i) => i.source);
153
153
 
154
154
  // 3. Wrap with platform-specific metadata (v0.11+)
155
- const exports = extractExportsWithAST(
155
+ const exports = await extractExportsWithAST(
156
156
  content,
157
157
  file,
158
158
  { domainKeywords: autoDetectedKeywords },
package/src/mapper.ts CHANGED
@@ -27,6 +27,12 @@ export interface MappingOptions {
27
27
 
28
28
  /**
29
29
  * Maps a single dependency node to a comprehensive ContextAnalysisResult.
30
+ *
31
+ * @param node - The dependency node to map
32
+ * @param graph - The full dependency graph
33
+ * @param clusters - All identified module clusters
34
+ * @param allCircularDeps - All identified circular dependencies
35
+ * @param options - Mapping options for detailed analysis
30
36
  */
31
37
  export function mapNodeToResult(
32
38
  node: DependencyNode,
@@ -69,7 +69,7 @@ export async function analyzeContext(
69
69
  }))
70
70
  );
71
71
 
72
- const graph = buildDependencyGraph(
72
+ const graph = await buildDependencyGraph(
73
73
  fileContents.filter((f) => !f.file.toLowerCase().endsWith('.py'))
74
74
  );
75
75
 
@@ -1,5 +1,14 @@
1
1
  import { analyzeContext } from '../analyzer';
2
2
  import { generateSummary } from '../summary';
3
+ import {
4
+ generateReportHead,
5
+ generateReportHero,
6
+ generateStatCards,
7
+ generateIssueSummary,
8
+ generateTable,
9
+ generateReportFooter,
10
+ wrapInCard,
11
+ } from '@aiready/core';
3
12
 
4
13
  /**
5
14
  * Generate HTML report
@@ -14,195 +23,78 @@ export function generateHTMLReport(
14
23
  // 'results' may be used in templates later; reference to avoid lint warnings
15
24
  void results;
16
25
 
17
- return `<!DOCTYPE html>
18
- <html lang="en">
19
- <head>
20
- <meta charset="UTF-8">
21
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
22
- <title>aiready Context Analysis Report</title>
23
- <style>
24
- body {
25
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
26
- line-height: 1.6;
27
- color: #333;
28
- max-width: 1200px;
29
- margin: 0 auto;
30
- padding: 20px;
31
- background-color: #f5f5f5;
32
- }
33
- h1, h2, h3 { color: #2c3e50; }
34
- .header {
35
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
36
- color: white;
37
- padding: 30px;
38
- border-radius: 8px;
39
- margin-bottom: 30px;
40
- }
41
- .summary {
42
- display: grid;
43
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
44
- gap: 20px;
45
- margin-bottom: 30px;
46
- }
47
- .card {
48
- background: white;
49
- padding: 20px;
50
- border-radius: 8px;
51
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
52
- }
53
- .metric {
54
- font-size: 2em;
55
- font-weight: bold;
56
- color: #667eea;
57
- }
58
- .label {
59
- color: #666;
60
- font-size: 0.9em;
61
- margin-top: 5px;
62
- }
63
- .issue-critical { color: #e74c3c; }
64
- .issue-major { color: #f39c12; }
65
- .issue-minor { color: #3498db; }
66
- table {
67
- width: 100%;
68
- border-collapse: collapse;
69
- background: white;
70
- border-radius: 8px;
71
- overflow: hidden;
72
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
73
- }
74
- th, td {
75
- padding: 12px;
76
- text-align: left;
77
- border-bottom: 1px solid #eee;
78
- }
79
- th {
80
- background-color: #667eea;
81
- color: white;
82
- font-weight: 600;
83
- }
84
- tr:hover { background-color: #f8f9fa; }
85
- .footer {
86
- text-align: center;
87
- margin-top: 40px;
88
- padding: 20px;
89
- color: #666;
90
- font-size: 0.9em;
91
- }
92
- </style>
93
- </head>
94
- <body>
95
- <div class="header">
96
- <h1>🔍 AIReady Context Analysis Report</h1>
97
- <p>Generated on ${new Date().toLocaleString()}</p>
98
- </div>
26
+ const head = generateReportHead('AIReady Context Analysis Report');
27
+
28
+ const stats = generateStatCards([
29
+ { value: summary.totalFiles, label: 'Files Analyzed' },
30
+ { value: summary.totalTokens.toLocaleString(), label: 'Total Tokens' },
31
+ { value: summary.avgContextBudget.toFixed(0), label: 'Avg Context Budget' },
32
+ {
33
+ value: totalIssues,
34
+ label: 'Total Issues',
35
+ color: totalIssues > 0 ? '#f39c12' : undefined,
36
+ },
37
+ ]);
38
+
39
+ const hero = generateReportHero(
40
+ '🔍 AIReady Context Analysis Report',
41
+ `Generated on ${new Date().toLocaleString()}`
42
+ );
99
43
 
100
- <div class="summary">
101
- <div class="card">
102
- <div class="metric">${summary.totalFiles}</div>
103
- <div class="label">Files Analyzed</div>
104
- </div>
105
- <div class="card">
106
- <div class="metric">${summary.totalTokens.toLocaleString()}</div>
107
- <div class="label">Total Tokens</div>
108
- </div>
109
- <div class="card">
110
- <div class="metric">${summary.avgContextBudget.toFixed(0)}</div>
111
- <div class="label">Avg Context Budget</div>
112
- </div>
113
- <div class="card">
114
- <div class="metric ${totalIssues > 0 ? 'issue-major' : ''}">${totalIssues}</div>
115
- <div class="label">Total Issues</div>
116
- </div>
117
- </div>
44
+ let body = `${hero}
45
+ ${stats}`;
118
46
 
119
- ${
120
- totalIssues > 0
121
- ? `
122
- <div class="card" style="margin-bottom: 30px;">
123
- <h2>⚠️ Issues Summary</h2>
124
- <p>
125
- <span class="issue-critical">🔴 Critical: ${summary.criticalIssues}</span> &nbsp;
126
- <span class="issue-major">🟡 Major: ${summary.majorIssues}</span> &nbsp;
127
- <span class="issue-minor">🔵 Minor: ${summary.minorIssues}</span>
128
- </p>
129
- <p><strong>Potential Savings:</strong> ${summary.totalPotentialSavings.toLocaleString()} tokens</p>
130
- </div>
131
- `
132
- : ''
47
+ if (totalIssues > 0) {
48
+ body += generateIssueSummary(
49
+ summary.criticalIssues,
50
+ summary.majorIssues,
51
+ summary.minorIssues,
52
+ summary.totalPotentialSavings
53
+ );
133
54
  }
134
55
 
135
- ${
136
- summary.fragmentedModules.length > 0
137
- ? `
138
- <div class="card" style="margin-bottom: 30px;">
139
- <h2>🧩 Fragmented Modules</h2>
140
- <table>
141
- <thead>
142
- <tr>
143
- <th>Domain</th>
144
- <th>Files</th>
145
- <th>Fragmentation</th>
146
- <th>Token Cost</th>
147
- </tr>
148
- </thead>
149
- <tbody>
150
- ${summary.fragmentedModules
151
- .map(
152
- (m) => `
153
- <tr>
154
- <td>${m.domain}</td>
155
- <td>${m.files.length}</td>
156
- <td>${(m.fragmentationScore * 100).toFixed(0)}%</td>
157
- <td>${m.totalTokens.toLocaleString()}</td>
158
- </tr>
159
- `
160
- )
161
- .join('')}
162
- </tbody>
163
- </table>
164
- </div>
165
- `
166
- : ''
56
+ if (summary.fragmentedModules.length > 0) {
57
+ const fragmentedRows = summary.fragmentedModules.map((m) => [
58
+ m.domain,
59
+ String(m.files.length),
60
+ `${(m.fragmentationScore * 100).toFixed(0)}%`,
61
+ m.totalTokens.toLocaleString(),
62
+ ]);
63
+ body += wrapInCard(
64
+ generateTable({
65
+ headers: ['Domain', 'Files', 'Fragmentation', 'Token Cost'],
66
+ rows: fragmentedRows,
67
+ }),
68
+ '🧩 Fragmented Modules'
69
+ );
167
70
  }
168
71
 
169
- ${
170
- summary.topExpensiveFiles.length > 0
171
- ? `
172
- <div class="card" style="margin-bottom: 30px;">
173
- <h2>💸 Most Expensive Files</h2>
174
- <table>
175
- <thead>
176
- <tr>
177
- <th>File</th>
178
- <th>Context Budget</th>
179
- <th>Severity</th>
180
- </tr>
181
- </thead>
182
- <tbody>
183
- ${summary.topExpensiveFiles
184
- .map(
185
- (f) => `
186
- <tr>
187
- <td>${f.file}</td>
188
- <td>${f.contextBudget.toLocaleString()} tokens</td>
189
- <td class="issue-${f.severity}">${f.severity.toUpperCase()}</td>
190
- </tr>
191
- `
192
- )
193
- .join('')}
194
- </tbody>
195
- </table>
196
- </div>
197
- `
198
- : ''
72
+ if (summary.topExpensiveFiles.length > 0) {
73
+ const expensiveRows = summary.topExpensiveFiles.map((f) => [
74
+ f.file,
75
+ `${f.contextBudget.toLocaleString()} tokens`,
76
+ `<span class="issue-${f.severity}">${f.severity.toUpperCase()}</span>`,
77
+ ]);
78
+ body += wrapInCard(
79
+ generateTable({
80
+ headers: ['File', 'Context Budget', 'Severity'],
81
+ rows: expensiveRows,
82
+ }),
83
+ '💸 Most Expensive Files'
84
+ );
199
85
  }
200
86
 
201
- <div class="footer">
202
- <p>Generated by <strong>@aiready/context-analyzer</strong></p>
203
- <p>Like AIReady? <a href="https://github.com/caopengau/aiready-context-analyzer">Star us on GitHub</a></p>
204
- <p>Found a bug? <a href="https://github.com/caopengau/aiready-context-analyzer/issues">Report it here</a></p>
205
- </div>
87
+ const footer = generateReportFooter({
88
+ title: 'Context Analysis Report',
89
+ packageName: 'context-analyzer',
90
+ packageUrl: 'https://github.com/caopengau/aiready-context-analyzer',
91
+ bugUrl: 'https://github.com/caopengau/aiready-context-analyzer/issues',
92
+ });
93
+
94
+ return `${head}
95
+ <body>
96
+ ${body}
97
+ ${footer}
206
98
  </body>
207
99
  </html>`;
208
100
  }