@aiready/context-analyzer 0.21.23 → 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 +11 -11
- package/.turbo/turbo-test.log +45 -58
- package/dist/chunk-AMPK6SWS.mjs +1754 -0
- package/dist/chunk-BHCRDEE4.mjs +1745 -0
- package/dist/chunk-IKRP7ECY.mjs +1754 -0
- package/dist/chunk-TWWPY7FD.mjs +1754 -0
- package/dist/chunk-Z5WY6A4P.mjs +1754 -0
- package/dist/cli.js +33 -29
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +33 -29
- 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__/fragmentation-coupling.test.ts +4 -4
- package/src/__tests__/python-context.test.ts +4 -2
- package/src/__tests__/report/html-report.test.ts +14 -3
- 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 +29 -21
|
@@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
2
2
|
import { buildDependencyGraph } from '../index';
|
|
3
3
|
|
|
4
4
|
describe('Auto-detection from folder structure', () => {
|
|
5
|
-
it('should auto-detect domain keywords from folder paths', () => {
|
|
5
|
+
it('should auto-detect domain keywords from folder paths', async () => {
|
|
6
6
|
const files = [
|
|
7
7
|
{
|
|
8
8
|
file: 'src/payments/process.ts',
|
|
@@ -14,7 +14,7 @@ describe('Auto-detection from folder structure', () => {
|
|
|
14
14
|
},
|
|
15
15
|
];
|
|
16
16
|
|
|
17
|
-
const graph = buildDependencyGraph(files);
|
|
17
|
+
const graph = await buildDependencyGraph(files);
|
|
18
18
|
const paymentsNode = graph.nodes.get('src/payments/process.ts');
|
|
19
19
|
const ordersNode = graph.nodes.get('src/orders/create.ts');
|
|
20
20
|
|
|
@@ -25,7 +25,7 @@ describe('Auto-detection from folder structure', () => {
|
|
|
25
25
|
expect(ordersNode?.exports[0].inferredDomain).toBe('order');
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
it('should detect domains from nested folders', () => {
|
|
28
|
+
it('should detect domains from nested folders', async () => {
|
|
29
29
|
const files = [
|
|
30
30
|
{
|
|
31
31
|
file: 'src/api/invoices/handler.ts',
|
|
@@ -33,14 +33,14 @@ describe('Auto-detection from folder structure', () => {
|
|
|
33
33
|
},
|
|
34
34
|
];
|
|
35
35
|
|
|
36
|
-
const graph = buildDependencyGraph(files);
|
|
36
|
+
const graph = await buildDependencyGraph(files);
|
|
37
37
|
const node = graph.nodes.get('src/api/invoices/handler.ts');
|
|
38
38
|
|
|
39
39
|
// Should detect 'invoice' from path (invoices folder)
|
|
40
40
|
expect(node?.exports[0].inferredDomain).toBe('invoice');
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
it('should skip common infrastructure folders', () => {
|
|
43
|
+
it('should skip common infrastructure folders', async () => {
|
|
44
44
|
const files = [
|
|
45
45
|
{
|
|
46
46
|
file: 'src/utils/helpers/format.ts',
|
|
@@ -48,14 +48,14 @@ describe('Auto-detection from folder structure', () => {
|
|
|
48
48
|
},
|
|
49
49
|
];
|
|
50
50
|
|
|
51
|
-
const graph = buildDependencyGraph(files);
|
|
51
|
+
const graph = await buildDependencyGraph(files);
|
|
52
52
|
const node = graph.nodes.get('src/utils/helpers/format.ts');
|
|
53
53
|
|
|
54
54
|
// 'utils' and 'helpers' should be skipped, no domain detected
|
|
55
55
|
expect(node?.exports[0].inferredDomain).toBe('unknown');
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
it('should merge auto-detected with custom keywords', () => {
|
|
58
|
+
it('should merge auto-detected with custom keywords', async () => {
|
|
59
59
|
const files = [
|
|
60
60
|
{
|
|
61
61
|
file: 'src/receipts/scan.ts',
|
|
@@ -63,7 +63,7 @@ describe('Auto-detection from folder structure', () => {
|
|
|
63
63
|
},
|
|
64
64
|
];
|
|
65
65
|
|
|
66
|
-
const graph = buildDependencyGraph(files, {
|
|
66
|
+
const graph = await buildDependencyGraph(files, {
|
|
67
67
|
domainKeywords: ['receipt'], // Custom keyword
|
|
68
68
|
});
|
|
69
69
|
const node = graph.nodes.get('src/receipts/scan.ts');
|
|
@@ -74,7 +74,7 @@ describe('Auto-detection from folder structure', () => {
|
|
|
74
74
|
});
|
|
75
75
|
|
|
76
76
|
describe('Import-path domain inference', () => {
|
|
77
|
-
it('should infer domain from import paths', () => {
|
|
77
|
+
it('should infer domain from import paths', async () => {
|
|
78
78
|
const files = [
|
|
79
79
|
{
|
|
80
80
|
file: 'src/lib/session.ts',
|
|
@@ -89,14 +89,14 @@ describe('Import-path domain inference', () => {
|
|
|
89
89
|
},
|
|
90
90
|
];
|
|
91
91
|
|
|
92
|
-
const graph = buildDependencyGraph(files);
|
|
92
|
+
const graph = await buildDependencyGraph(files);
|
|
93
93
|
const sessionNode = graph.nodes.get('src/lib/session.ts');
|
|
94
94
|
|
|
95
95
|
// session.ts imports from '../payments/...' so should infer 'payment' domain
|
|
96
96
|
expect(sessionNode?.exports[0].inferredDomain).toBe('payment');
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
-
it('should infer domain from absolute import paths', () => {
|
|
99
|
+
it('should infer domain from absolute import paths', async () => {
|
|
100
100
|
const files = [
|
|
101
101
|
{
|
|
102
102
|
file: 'src/components/nav-links.ts',
|
|
@@ -111,14 +111,14 @@ describe('Import-path domain inference', () => {
|
|
|
111
111
|
},
|
|
112
112
|
];
|
|
113
113
|
|
|
114
|
-
const graph = buildDependencyGraph(files);
|
|
114
|
+
const graph = await buildDependencyGraph(files);
|
|
115
115
|
const navNode = graph.nodes.get('src/components/nav-links.ts');
|
|
116
116
|
|
|
117
117
|
// nav-links.ts imports from '@/orders/...' so should infer 'order' domain
|
|
118
118
|
expect(navNode?.exports[0].inferredDomain).toBe('order');
|
|
119
119
|
});
|
|
120
120
|
|
|
121
|
-
it('should use identifier name first before import-path fallback', () => {
|
|
121
|
+
it('should use identifier name first before import-path fallback', async () => {
|
|
122
122
|
const files = [
|
|
123
123
|
{
|
|
124
124
|
file: 'src/lib/handler.ts',
|
|
@@ -129,14 +129,14 @@ describe('Import-path domain inference', () => {
|
|
|
129
129
|
},
|
|
130
130
|
];
|
|
131
131
|
|
|
132
|
-
const graph = buildDependencyGraph(files);
|
|
132
|
+
const graph = await buildDependencyGraph(files);
|
|
133
133
|
const node = graph.nodes.get('src/lib/handler.ts');
|
|
134
134
|
|
|
135
135
|
// processInvoice should match 'invoice' from identifier, not 'payment' from imports
|
|
136
136
|
expect(node?.exports[0].inferredDomain).toBe('invoice');
|
|
137
137
|
});
|
|
138
138
|
|
|
139
|
-
it('should fall back to import-path when identifier is generic', () => {
|
|
139
|
+
it('should fall back to import-path when identifier is generic', async () => {
|
|
140
140
|
const files = [
|
|
141
141
|
{
|
|
142
142
|
file: 'src/lib/dynamodb.ts',
|
|
@@ -147,7 +147,7 @@ describe('Import-path domain inference', () => {
|
|
|
147
147
|
},
|
|
148
148
|
];
|
|
149
149
|
|
|
150
|
-
const graph = buildDependencyGraph(files);
|
|
150
|
+
const graph = await buildDependencyGraph(files);
|
|
151
151
|
const node = graph.nodes.get('src/lib/dynamodb.ts');
|
|
152
152
|
|
|
153
153
|
// 'connect' is generic, should infer 'customer' from import path
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from '../index';
|
|
7
7
|
|
|
8
8
|
describe('fragmentation coupling discount', () => {
|
|
9
|
-
it('does not apply discount when files have no shared imports', () => {
|
|
9
|
+
it('does not apply discount when files have no shared imports', async () => {
|
|
10
10
|
const files = [
|
|
11
11
|
{
|
|
12
12
|
file: 'src/billing/a.ts',
|
|
@@ -22,7 +22,7 @@ describe('fragmentation coupling discount', () => {
|
|
|
22
22
|
},
|
|
23
23
|
];
|
|
24
24
|
|
|
25
|
-
const graph = buildDependencyGraph(files);
|
|
25
|
+
const graph = await buildDependencyGraph(files);
|
|
26
26
|
const clusters = detectModuleClusters(graph);
|
|
27
27
|
const cluster = clusters.find((c) => c.domain === 'billing');
|
|
28
28
|
expect(cluster).toBeDefined();
|
|
@@ -38,7 +38,7 @@ describe('fragmentation coupling discount', () => {
|
|
|
38
38
|
expect(cluster!.fragmentationScore).toBeCloseTo(expected, 6);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
it('applies up-to-20% discount when files share identical imports', () => {
|
|
41
|
+
it('applies up-to-20% discount when files share identical imports', async () => {
|
|
42
42
|
const files = [
|
|
43
43
|
{
|
|
44
44
|
file: 'src/billing/a.ts',
|
|
@@ -54,7 +54,7 @@ describe('fragmentation coupling discount', () => {
|
|
|
54
54
|
},
|
|
55
55
|
];
|
|
56
56
|
|
|
57
|
-
const graph = buildDependencyGraph(files);
|
|
57
|
+
const graph = await buildDependencyGraph(files);
|
|
58
58
|
const clusters = detectModuleClusters(graph);
|
|
59
59
|
const cluster = clusters.find((c) => c.domain === 'billing');
|
|
60
60
|
expect(cluster).toBeDefined();
|
|
@@ -3,9 +3,10 @@ import * as pythonContext from '../analyzers/python-context';
|
|
|
3
3
|
|
|
4
4
|
// Mock @aiready/core
|
|
5
5
|
vi.mock('@aiready/core', () => ({
|
|
6
|
-
getParser: vi.fn((filename: string) => {
|
|
6
|
+
getParser: vi.fn(async (filename: string) => {
|
|
7
7
|
if (filename.endsWith('.py')) {
|
|
8
8
|
return {
|
|
9
|
+
initialize: vi.fn().mockResolvedValue(undefined),
|
|
9
10
|
parse: vi.fn(() => ({
|
|
10
11
|
imports: [
|
|
11
12
|
{ source: 'os', specifiers: ['path'], isRelative: false },
|
|
@@ -16,6 +17,7 @@ vi.mock('@aiready/core', () => ({
|
|
|
16
17
|
{ name: 'my_function', type: 'function' },
|
|
17
18
|
],
|
|
18
19
|
})),
|
|
20
|
+
language: 'python',
|
|
19
21
|
};
|
|
20
22
|
}
|
|
21
23
|
return null;
|
|
@@ -39,7 +41,7 @@ describe('python-context', () => {
|
|
|
39
41
|
describe('analyzePythonContext', () => {
|
|
40
42
|
it('should return empty array when parser is not available', async () => {
|
|
41
43
|
const { getParser } = await import('@aiready/core');
|
|
42
|
-
vi.mocked(getParser).
|
|
44
|
+
vi.mocked(getParser).mockResolvedValueOnce(null);
|
|
43
45
|
|
|
44
46
|
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
45
47
|
|
|
@@ -11,6 +11,17 @@ vi.mock('@aiready/core', () => ({
|
|
|
11
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
12
|
generateReportFooter: (options: any) =>
|
|
13
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>`,
|
|
14
25
|
}));
|
|
15
26
|
|
|
16
27
|
describe('generateHTMLReport', () => {
|
|
@@ -98,11 +109,11 @@ describe('generateHTMLReport', () => {
|
|
|
98
109
|
|
|
99
110
|
const html = generateHTMLReport(summary, results);
|
|
100
111
|
|
|
101
|
-
expect(html).toContain('Issues
|
|
112
|
+
expect(html).toContain('Issues:');
|
|
102
113
|
expect(html).toContain('Critical:');
|
|
103
114
|
expect(html).toContain('Major:');
|
|
104
115
|
expect(html).toContain('Minor:');
|
|
105
|
-
expect(html).toContain('
|
|
116
|
+
expect(html).toContain('Savings');
|
|
106
117
|
});
|
|
107
118
|
|
|
108
119
|
it('should not include issues section when no issues', () => {
|
|
@@ -227,6 +238,6 @@ describe('generateHTMLReport', () => {
|
|
|
227
238
|
const html = generateHTMLReport(summary, results);
|
|
228
239
|
|
|
229
240
|
// Should show issues section
|
|
230
|
-
expect(html).toContain('Issues
|
|
241
|
+
expect(html).toContain('Issues:');
|
|
231
242
|
});
|
|
232
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(
|
|
63
|
+
expect(result.factors.some((f: any) => f.name === 'Import Depth')).toBe(
|
|
64
|
+
true
|
|
65
|
+
);
|
|
64
66
|
expect(
|
|
65
|
-
result.recommendations.some((r) =>
|
|
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(
|
|
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(
|
|
112
|
-
|
|
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
|
package/src/graph-builder.ts
CHANGED
|
@@ -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,
|
package/src/orchestrator.ts
CHANGED
|
@@ -2,9 +2,12 @@ import { analyzeContext } from '../analyzer';
|
|
|
2
2
|
import { generateSummary } from '../summary';
|
|
3
3
|
import {
|
|
4
4
|
generateReportHead,
|
|
5
|
+
generateReportHero,
|
|
5
6
|
generateStatCards,
|
|
7
|
+
generateIssueSummary,
|
|
6
8
|
generateTable,
|
|
7
9
|
generateReportFooter,
|
|
10
|
+
wrapInCard,
|
|
8
11
|
} from '@aiready/core';
|
|
9
12
|
|
|
10
13
|
/**
|
|
@@ -33,22 +36,21 @@ export function generateHTMLReport(
|
|
|
33
36
|
},
|
|
34
37
|
]);
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
const hero = generateReportHero(
|
|
40
|
+
'🔍 AIReady Context Analysis Report',
|
|
41
|
+
`Generated on ${new Date().toLocaleString()}`
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
let body = `${hero}
|
|
40
45
|
${stats}`;
|
|
41
46
|
|
|
42
47
|
if (totalIssues > 0) {
|
|
43
|
-
body +=
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
</p>
|
|
50
|
-
<p><strong>Potential Savings:</strong> ${summary.totalPotentialSavings.toLocaleString()} tokens</p>
|
|
51
|
-
</div>`;
|
|
48
|
+
body += generateIssueSummary(
|
|
49
|
+
summary.criticalIssues,
|
|
50
|
+
summary.majorIssues,
|
|
51
|
+
summary.minorIssues,
|
|
52
|
+
summary.totalPotentialSavings
|
|
53
|
+
);
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
if (summary.fragmentedModules.length > 0) {
|
|
@@ -58,10 +60,13 @@ ${stats}`;
|
|
|
58
60
|
`${(m.fragmentationScore * 100).toFixed(0)}%`,
|
|
59
61
|
m.totalTokens.toLocaleString(),
|
|
60
62
|
]);
|
|
61
|
-
body +=
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
body += wrapInCard(
|
|
64
|
+
generateTable({
|
|
65
|
+
headers: ['Domain', 'Files', 'Fragmentation', 'Token Cost'],
|
|
66
|
+
rows: fragmentedRows,
|
|
67
|
+
}),
|
|
68
|
+
'🧩 Fragmented Modules'
|
|
69
|
+
);
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
if (summary.topExpensiveFiles.length > 0) {
|
|
@@ -70,10 +75,13 @@ ${stats}`;
|
|
|
70
75
|
`${f.contextBudget.toLocaleString()} tokens`,
|
|
71
76
|
`<span class="issue-${f.severity}">${f.severity.toUpperCase()}</span>`,
|
|
72
77
|
]);
|
|
73
|
-
body +=
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
78
|
+
body += wrapInCard(
|
|
79
|
+
generateTable({
|
|
80
|
+
headers: ['File', 'Context Budget', 'Severity'],
|
|
81
|
+
rows: expensiveRows,
|
|
82
|
+
}),
|
|
83
|
+
'💸 Most Expensive Files'
|
|
84
|
+
);
|
|
77
85
|
}
|
|
78
86
|
|
|
79
87
|
const footer = generateReportFooter({
|