@aiready/context-analyzer 0.21.5 → 0.21.7
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/.aiready/aiready-report-20260314-222254.json +39216 -0
- package/.aiready/aiready-report-20260314-223947.json +3413 -0
- package/.aiready/aiready-report-20260314-224112.json +3413 -0
- package/.aiready/aiready-report-20260314-224302.json +2973 -0
- package/.aiready/aiready-report-20260314-224939.json +3092 -0
- package/.aiready/aiready-report-20260314-225154.json +3092 -0
- package/.turbo/turbo-build.log +26 -24
- package/.turbo/turbo-lint.log +5 -6
- package/.turbo/turbo-test.log +41 -119
- package/dist/__tests__/analyzer.test.js +55 -14
- package/dist/__tests__/analyzer.test.js.map +1 -1
- package/dist/__tests__/cluster-detector.test.d.ts +2 -0
- package/dist/__tests__/cluster-detector.test.d.ts.map +1 -0
- package/dist/__tests__/cluster-detector.test.js +121 -0
- package/dist/__tests__/cluster-detector.test.js.map +1 -0
- package/dist/__tests__/contract.test.d.ts +2 -0
- package/dist/__tests__/contract.test.d.ts.map +1 -0
- package/dist/__tests__/contract.test.js +59 -0
- package/dist/__tests__/contract.test.js.map +1 -0
- package/dist/__tests__/enhanced-cohesion.test.js +12 -2
- package/dist/__tests__/enhanced-cohesion.test.js.map +1 -1
- package/dist/__tests__/file-classification.test.d.ts +2 -0
- package/dist/__tests__/file-classification.test.d.ts.map +1 -0
- package/dist/__tests__/file-classification.test.js +749 -0
- package/dist/__tests__/file-classification.test.js.map +1 -0
- package/dist/__tests__/fragmentation-advanced.test.js +2 -8
- package/dist/__tests__/fragmentation-advanced.test.js.map +1 -1
- package/dist/__tests__/fragmentation-coupling.test.js +2 -2
- package/dist/__tests__/fragmentation-coupling.test.js.map +1 -1
- package/dist/__tests__/fragmentation-log.test.js +3 -7
- package/dist/__tests__/fragmentation-log.test.js.map +1 -1
- package/dist/__tests__/provider.test.d.ts +2 -0
- package/dist/__tests__/provider.test.d.ts.map +1 -0
- package/dist/__tests__/provider.test.js +72 -0
- package/dist/__tests__/provider.test.js.map +1 -0
- package/dist/__tests__/remediation.test.d.ts +2 -0
- package/dist/__tests__/remediation.test.d.ts.map +1 -0
- package/dist/__tests__/remediation.test.js +61 -0
- package/dist/__tests__/remediation.test.js.map +1 -0
- package/dist/__tests__/scoring.test.js +196 -16
- package/dist/__tests__/scoring.test.js.map +1 -1
- package/dist/__tests__/structural-cohesion.test.js +8 -2
- package/dist/__tests__/structural-cohesion.test.js.map +1 -1
- package/dist/analyzer.d.ts +31 -94
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +260 -678
- package/dist/analyzer.js.map +1 -1
- package/dist/analyzers/python-context.d.ts.map +1 -1
- package/dist/analyzers/python-context.js +10 -8
- package/dist/analyzers/python-context.js.map +1 -1
- package/dist/ast-utils.d.ts +16 -0
- package/dist/ast-utils.d.ts.map +1 -0
- package/dist/ast-utils.js +81 -0
- package/dist/ast-utils.js.map +1 -0
- package/dist/chunk-2HE27YEV.mjs +1739 -0
- package/dist/chunk-64U3PNO3.mjs +94 -0
- package/dist/chunk-CDIVYADN.mjs +2110 -0
- package/dist/chunk-D25B5LZR.mjs +1739 -0
- package/dist/chunk-D3SIHB2V.mjs +2118 -0
- package/dist/chunk-FNPSK3CG.mjs +1760 -0
- package/dist/chunk-GXTGOLZT.mjs +92 -0
- package/dist/chunk-KDUUZQBK.mjs +1692 -0
- package/dist/chunk-KWIS5FQP.mjs +1739 -0
- package/dist/chunk-LERPI33Y.mjs +2060 -0
- package/dist/chunk-MZP3G7TF.mjs +2118 -0
- package/dist/chunk-NOHK5DLU.mjs +2173 -0
- package/dist/chunk-ORLC5Y4J.mjs +1787 -0
- package/dist/chunk-OTCQL7DY.mjs +2045 -0
- package/dist/chunk-RRB2C34Q.mjs +1738 -0
- package/dist/chunk-SFK6XTJE.mjs +2110 -0
- package/dist/chunk-U5R2FTCR.mjs +1803 -0
- package/dist/chunk-UU4HZ7ZT.mjs +1849 -0
- package/dist/chunk-WKOZOHOU.mjs +2060 -0
- package/dist/chunk-XIXAWCMS.mjs +1760 -0
- package/dist/chunk-XTAXUNQN.mjs +1742 -0
- package/dist/classifier.d.ts +114 -0
- package/dist/classifier.d.ts.map +1 -0
- package/dist/classifier.js +439 -0
- package/dist/classifier.js.map +1 -0
- package/dist/cli.js +681 -1170
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +63 -533
- package/dist/cluster-detector.d.ts +8 -0
- package/dist/cluster-detector.d.ts.map +1 -0
- package/dist/cluster-detector.js +70 -0
- package/dist/cluster-detector.js.map +1 -0
- package/dist/defaults.d.ts +7 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +54 -0
- package/dist/defaults.js.map +1 -0
- package/dist/graph-builder.d.ts +33 -0
- package/dist/graph-builder.d.ts.map +1 -0
- package/dist/graph-builder.js +225 -0
- package/dist/graph-builder.js.map +1 -0
- package/dist/index.d.mts +93 -106
- package/dist/index.d.ts +93 -106
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +932 -745
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +262 -28
- package/dist/metrics.d.ts +34 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +170 -0
- package/dist/metrics.js.map +1 -0
- package/dist/provider.d.ts +6 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +48 -0
- package/dist/provider.js.map +1 -0
- package/dist/python-context-3GZKN3LR.mjs +162 -0
- package/dist/python-context-O2EN3M6Z.mjs +162 -0
- package/dist/remediation.d.ts +25 -0
- package/dist/remediation.d.ts.map +1 -0
- package/dist/remediation.js +98 -0
- package/dist/remediation.js.map +1 -0
- package/dist/scoring.d.ts +3 -7
- package/dist/scoring.d.ts.map +1 -1
- package/dist/scoring.js +57 -48
- package/dist/scoring.js.map +1 -1
- package/dist/semantic-analysis.d.ts +12 -23
- package/dist/semantic-analysis.d.ts.map +1 -1
- package/dist/semantic-analysis.js +172 -110
- package/dist/semantic-analysis.js.map +1 -1
- package/dist/summary.d.ts +6 -0
- package/dist/summary.d.ts.map +1 -0
- package/dist/summary.js +92 -0
- package/dist/summary.js.map +1 -0
- package/dist/types.d.ts +9 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/output-formatter.d.ts +14 -0
- package/dist/utils/output-formatter.d.ts.map +1 -0
- package/dist/utils/output-formatter.js +338 -0
- package/dist/utils/output-formatter.js.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/analyzer.test.ts +1 -1
- package/src/__tests__/auto-detection.test.ts +1 -1
- package/src/__tests__/contract.test.ts +1 -1
- package/src/__tests__/enhanced-cohesion.test.ts +1 -1
- package/src/__tests__/file-classification.test.ts +1 -1
- package/src/__tests__/fragmentation-advanced.test.ts +1 -1
- package/src/__tests__/fragmentation-coupling.test.ts +1 -1
- package/src/__tests__/fragmentation-log.test.ts +1 -1
- package/src/__tests__/provider.test.ts +1 -1
- package/src/__tests__/structural-cohesion.test.ts +1 -1
- package/src/analyzer.ts +112 -317
- package/src/analyzers/python-context.ts +7 -76
- package/src/ast-utils.ts +2 -2
- package/src/classifier.ts +13 -328
- package/src/cli-action.ts +110 -0
- package/src/cli.ts +3 -701
- package/src/cluster-detector.ts +28 -1
- package/src/defaults.ts +3 -0
- package/src/graph-builder.ts +10 -91
- package/src/heuristics.ts +216 -0
- package/src/index.ts +6 -0
- package/src/issue-analyzer.ts +158 -0
- package/src/metrics.ts +9 -0
- package/src/scoring.ts +3 -5
- package/src/semantic-analysis.ts +8 -14
- package/src/summary.ts +62 -106
- package/src/types.ts +52 -20
- package/src/utils/dependency-graph-utils.ts +126 -0
- package/src/utils/output-formatter.ts +411 -0
- package/src/utils/string-utils.ts +21 -0
package/src/summary.ts
CHANGED
|
@@ -3,15 +3,15 @@ import type {
|
|
|
3
3
|
ContextSummary,
|
|
4
4
|
ModuleCluster,
|
|
5
5
|
} from './types';
|
|
6
|
-
import { calculatePathEntropy, calculateDirectoryDistance } from './analyzer';
|
|
7
6
|
import { GLOBAL_SCAN_OPTIONS } from '@aiready/core';
|
|
7
|
+
import { calculatePathEntropy } from './metrics';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Generate summary of context analysis results
|
|
11
11
|
*/
|
|
12
12
|
export function generateSummary(
|
|
13
13
|
results: ContextAnalysisResult[],
|
|
14
|
-
options
|
|
14
|
+
options: any = {}
|
|
15
15
|
): ContextSummary {
|
|
16
16
|
const config = options
|
|
17
17
|
? Object.fromEntries(
|
|
@@ -19,137 +19,92 @@ export function generateSummary(
|
|
|
19
19
|
([key]) => !GLOBAL_SCAN_OPTIONS.includes(key) || key === 'rootDir'
|
|
20
20
|
)
|
|
21
21
|
)
|
|
22
|
-
:
|
|
23
|
-
|
|
24
|
-
if (results.length === 0) {
|
|
25
|
-
return {
|
|
26
|
-
totalFiles: 0,
|
|
27
|
-
totalTokens: 0,
|
|
28
|
-
avgContextBudget: 0,
|
|
29
|
-
maxContextBudget: 0,
|
|
30
|
-
avgImportDepth: 0,
|
|
31
|
-
maxImportDepth: 0,
|
|
32
|
-
deepFiles: [],
|
|
33
|
-
avgFragmentation: 0,
|
|
34
|
-
fragmentedModules: [],
|
|
35
|
-
avgCohesion: 0,
|
|
36
|
-
lowCohesionFiles: [],
|
|
37
|
-
criticalIssues: 0,
|
|
38
|
-
majorIssues: 0,
|
|
39
|
-
minorIssues: 0,
|
|
40
|
-
totalPotentialSavings: 0,
|
|
41
|
-
topExpensiveFiles: [],
|
|
42
|
-
config,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
22
|
+
: {};
|
|
45
23
|
|
|
46
24
|
const totalFiles = results.length;
|
|
47
25
|
const totalTokens = results.reduce((sum, r) => sum + r.tokenCost, 0);
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const avgContextBudget = totalContextBudget / totalFiles;
|
|
53
|
-
const maxContextBudget = Math.max(...results.map((r) => r.contextBudget));
|
|
54
|
-
|
|
55
|
-
const avgImportDepth =
|
|
56
|
-
results.reduce((sum, r) => sum + r.importDepth, 0) / totalFiles;
|
|
57
|
-
const maxImportDepth = Math.max(...results.map((r) => r.importDepth));
|
|
26
|
+
const avgContextBudget =
|
|
27
|
+
totalFiles > 0
|
|
28
|
+
? results.reduce((sum, r) => sum + r.contextBudget, 0) / totalFiles
|
|
29
|
+
: 0;
|
|
58
30
|
|
|
31
|
+
// Find deep files
|
|
59
32
|
const deepFiles = results
|
|
60
|
-
.filter((r) => r.importDepth
|
|
61
|
-
.map((r) => ({ file: r.file, depth: r.importDepth }))
|
|
62
|
-
.sort((a, b) => b.depth - a.depth)
|
|
63
|
-
.slice(0, 10);
|
|
33
|
+
.filter((r) => r.importDepth > 5)
|
|
34
|
+
.map((r) => ({ file: r.file, depth: r.importDepth }));
|
|
64
35
|
|
|
65
|
-
const
|
|
66
|
-
results.reduce((sum, r) => sum + r.fragmentationScore, 0) / totalFiles;
|
|
36
|
+
const maxImportDepth = Math.max(0, ...results.map((r) => r.importDepth));
|
|
67
37
|
|
|
38
|
+
// Find fragmented modules (clusters)
|
|
68
39
|
const moduleMap = new Map<string, ContextAnalysisResult[]>();
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
40
|
+
results.forEach((r) => {
|
|
41
|
+
const parts = r.file.split('/');
|
|
42
|
+
// Try to identify domain/module (e.g., packages/core, src/utils)
|
|
43
|
+
let domain = 'root';
|
|
44
|
+
if (parts.length > 2) {
|
|
45
|
+
domain = parts.slice(0, 2).join('/');
|
|
73
46
|
}
|
|
74
|
-
|
|
47
|
+
if (!moduleMap.has(domain)) moduleMap.set(domain, []);
|
|
48
|
+
moduleMap.get(domain)!.push(r);
|
|
49
|
+
});
|
|
75
50
|
|
|
76
51
|
const fragmentedModules: ModuleCluster[] = [];
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const fragmentationScore =
|
|
80
|
-
files.reduce((sum, f) => sum + f.fragmentationScore, 0) / files.length;
|
|
81
|
-
if (fragmentationScore < 0.3) continue;
|
|
82
|
-
|
|
83
|
-
const totalTokens = files.reduce((sum, f) => sum + f.tokenCost, 0);
|
|
84
|
-
const avgCohesion =
|
|
85
|
-
files.reduce((sum, f) => sum + f.cohesionScore, 0) / files.length;
|
|
86
|
-
const targetFiles = Math.max(1, Math.ceil(files.length / 3));
|
|
87
|
-
|
|
52
|
+
moduleMap.forEach((files, domain) => {
|
|
53
|
+
const clusterTokens = files.reduce((sum, f) => sum + f.tokenCost, 0);
|
|
88
54
|
const filePaths = files.map((f) => f.file);
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
55
|
+
const avgEntropy = calculatePathEntropy(filePaths);
|
|
56
|
+
|
|
57
|
+
// A module is fragmented if it has many files with high directory distance
|
|
58
|
+
// and relatively low cohesion
|
|
59
|
+
const fragmentationScore = Math.min(1, avgEntropy * (files.length / 10));
|
|
60
|
+
|
|
61
|
+
if (fragmentationScore > 0.4) {
|
|
62
|
+
fragmentedModules.push({
|
|
63
|
+
domain,
|
|
64
|
+
files: filePaths,
|
|
65
|
+
fragmentationScore,
|
|
66
|
+
totalTokens: clusterTokens,
|
|
67
|
+
avgCohesion:
|
|
68
|
+
files.reduce((sum, f) => sum + f.cohesionScore, 0) / files.length,
|
|
69
|
+
suggestedStructure: {
|
|
70
|
+
targetFiles: Math.ceil(files.length / 2),
|
|
71
|
+
consolidationPlan: [
|
|
72
|
+
`Consolidate ${files.length} files in ${domain} into fewer modules`,
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
});
|
|
99
76
|
}
|
|
77
|
+
});
|
|
100
78
|
|
|
101
|
-
|
|
102
|
-
let importPairs = 0;
|
|
103
|
-
for (let i = 0; i < files.length; i++) {
|
|
104
|
-
for (let j = i + 1; j < files.length; j++) {
|
|
105
|
-
importSimTotal += jaccard(
|
|
106
|
-
files[i].dependencyList || [],
|
|
107
|
-
files[j].dependencyList || []
|
|
108
|
-
);
|
|
109
|
-
importPairs++;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
79
|
+
fragmentedModules.sort((a, b) => b.fragmentationScore - a.fragmentationScore);
|
|
112
80
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
totalTokens,
|
|
119
|
-
fragmentationScore,
|
|
120
|
-
avgCohesion,
|
|
121
|
-
importCohesion,
|
|
122
|
-
pathEntropy,
|
|
123
|
-
directoryDistance,
|
|
124
|
-
suggestedStructure: {
|
|
125
|
-
targetFiles,
|
|
126
|
-
consolidationPlan: [
|
|
127
|
-
`Consolidate ${files.length} files across ${new Set(files.map((f) => f.file.split('/').slice(0, -1).join('/'))).size} directories`,
|
|
128
|
-
`Target ~${targetFiles} core modules to reduce context switching`,
|
|
129
|
-
],
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
}
|
|
81
|
+
const avgFragmentation =
|
|
82
|
+
fragmentedModules.length > 0
|
|
83
|
+
? fragmentedModules.reduce((sum, m) => sum + m.fragmentationScore, 0) /
|
|
84
|
+
fragmentedModules.length
|
|
85
|
+
: 0;
|
|
133
86
|
|
|
87
|
+
// Cohesion
|
|
134
88
|
const avgCohesion =
|
|
135
|
-
results.reduce((sum, r) => sum + r.cohesionScore, 0) / totalFiles;
|
|
89
|
+
results.reduce((sum, r) => sum + r.cohesionScore, 0) / (totalFiles || 1);
|
|
90
|
+
|
|
136
91
|
const lowCohesionFiles = results
|
|
137
92
|
.filter((r) => r.cohesionScore < 0.4)
|
|
138
|
-
.map((r) => ({ file: r.file, score: r.cohesionScore }))
|
|
139
|
-
.sort((a, b) => a.score - b.score)
|
|
140
|
-
.slice(0, 10);
|
|
93
|
+
.map((r) => ({ file: r.file, score: r.cohesionScore }));
|
|
141
94
|
|
|
95
|
+
// Issues
|
|
142
96
|
const criticalIssues = results.filter(
|
|
143
97
|
(r) => r.severity === 'critical'
|
|
144
98
|
).length;
|
|
145
99
|
const majorIssues = results.filter((r) => r.severity === 'major').length;
|
|
146
100
|
const minorIssues = results.filter((r) => r.severity === 'minor').length;
|
|
101
|
+
|
|
147
102
|
const totalPotentialSavings = results.reduce(
|
|
148
|
-
(sum, r) => sum + r.potentialSavings,
|
|
103
|
+
(sum, r) => sum + (r.potentialSavings || 0),
|
|
149
104
|
0
|
|
150
105
|
);
|
|
151
106
|
|
|
152
|
-
const topExpensiveFiles = results
|
|
107
|
+
const topExpensiveFiles = [...results]
|
|
153
108
|
.sort((a, b) => b.contextBudget - a.contextBudget)
|
|
154
109
|
.slice(0, 10)
|
|
155
110
|
.map((r) => ({
|
|
@@ -162,8 +117,9 @@ export function generateSummary(
|
|
|
162
117
|
totalFiles,
|
|
163
118
|
totalTokens,
|
|
164
119
|
avgContextBudget,
|
|
165
|
-
maxContextBudget,
|
|
166
|
-
avgImportDepth
|
|
120
|
+
maxContextBudget: Math.max(0, ...results.map((r) => r.contextBudget)),
|
|
121
|
+
avgImportDepth:
|
|
122
|
+
results.reduce((sum, r) => sum + r.importDepth, 0) / (totalFiles || 1),
|
|
167
123
|
maxImportDepth,
|
|
168
124
|
deepFiles,
|
|
169
125
|
avgFragmentation,
|
package/src/types.ts
CHANGED
|
@@ -1,45 +1,77 @@
|
|
|
1
1
|
import type { ScanOptions, Severity } from '@aiready/core';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Options for the Context Analyzer tool.
|
|
5
|
+
* Controls thresholds for import depth, context budget, and cohesion.
|
|
6
|
+
*/
|
|
3
7
|
export interface ContextAnalyzerOptions extends ScanOptions {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
/** Maximum acceptable import depth (default: 5) */
|
|
9
|
+
maxDepth?: number;
|
|
10
|
+
/** Maximum acceptable token budget for a single context (default: 25000) */
|
|
11
|
+
maxContextBudget?: number;
|
|
12
|
+
/** Minimum acceptable cohesion score between 0 and 1 (default: 0.6) */
|
|
13
|
+
minCohesion?: number;
|
|
14
|
+
/** Maximum acceptable fragmentation score between 0 and 1 (default: 0.5) */
|
|
15
|
+
maxFragmentation?: number;
|
|
16
|
+
/** Analysis focus area: fragmentation, cohesion, depth, or all (default: 'all') */
|
|
17
|
+
focus?: 'fragmentation' | 'cohesion' | 'depth' | 'all';
|
|
18
|
+
/** Whether to include node_modules in the analysis (default: false) */
|
|
19
|
+
includeNodeModules?: boolean;
|
|
10
20
|
}
|
|
11
21
|
|
|
22
|
+
/**
|
|
23
|
+
* The result of a context analysis for a single file or module.
|
|
24
|
+
* Includes metrics for tokens, dependencies, cohesion, and AI impact.
|
|
25
|
+
*/
|
|
12
26
|
export interface ContextAnalysisResult {
|
|
27
|
+
/** The file path being analyzed */
|
|
13
28
|
file: string;
|
|
14
29
|
|
|
15
30
|
// Basic metrics
|
|
16
|
-
|
|
31
|
+
/** Total number of tokens in this file */
|
|
32
|
+
tokenCost: number;
|
|
33
|
+
/** Total lines of code in the file */
|
|
17
34
|
linesOfCode: number;
|
|
18
35
|
|
|
19
36
|
// Dependency analysis
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
37
|
+
/** Maximum depth of the import tree for this file */
|
|
38
|
+
importDepth: number;
|
|
39
|
+
/** Total number of transitive dependencies */
|
|
40
|
+
dependencyCount: number;
|
|
41
|
+
/** List of all files in the dependency tree */
|
|
42
|
+
dependencyList: string[];
|
|
43
|
+
/** Detected circular dependency chains */
|
|
44
|
+
circularDeps: string[][];
|
|
24
45
|
|
|
25
46
|
// Cohesion analysis
|
|
26
|
-
|
|
27
|
-
|
|
47
|
+
/** Cohesion score from 0 to 1 (1 is perfect cohesion) */
|
|
48
|
+
cohesionScore: number;
|
|
49
|
+
/** Detected domain categories for the module */
|
|
50
|
+
domains: string[];
|
|
51
|
+
/** Number of exported symbols */
|
|
28
52
|
exportCount: number;
|
|
29
53
|
|
|
30
54
|
// AI context impact
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
55
|
+
/** Total tokens required to understand this file and all its dependencies */
|
|
56
|
+
contextBudget: number;
|
|
57
|
+
/** Fragmentation score from 0 to 1 (0 is well-grouped) */
|
|
58
|
+
fragmentationScore: number;
|
|
59
|
+
/** List of files that should be loaded together for full context */
|
|
60
|
+
relatedFiles: string[];
|
|
34
61
|
|
|
35
62
|
// File classification (NEW)
|
|
36
|
-
|
|
63
|
+
/** The semantic classification of the file (e.g. 'barrel-export', 'service-file') */
|
|
64
|
+
fileClassification: FileClassification;
|
|
37
65
|
|
|
38
66
|
// Recommendations
|
|
67
|
+
/** Overall severity of identified issues */
|
|
39
68
|
severity: Severity | 'critical' | 'major' | 'minor' | 'info';
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
69
|
+
/** List of specific structural problems found */
|
|
70
|
+
issues: string[];
|
|
71
|
+
/** Actionable suggestions for improving context readiness */
|
|
72
|
+
recommendations: string[];
|
|
73
|
+
/** Estimated tokens that could be saved by following recommendations */
|
|
74
|
+
potentialSavings: number;
|
|
43
75
|
}
|
|
44
76
|
|
|
45
77
|
/**
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared dependency graph utilities used by context analyzers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export function calculateImportDepthFromEdges(
|
|
6
|
+
file: string,
|
|
7
|
+
edges: Map<string, Set<string>>,
|
|
8
|
+
visited = new Set<string>(),
|
|
9
|
+
depth = 0
|
|
10
|
+
): number {
|
|
11
|
+
if (visited.has(file)) return depth;
|
|
12
|
+
|
|
13
|
+
const dependencies = edges.get(file);
|
|
14
|
+
if (!dependencies || dependencies.size === 0) return depth;
|
|
15
|
+
|
|
16
|
+
const nextVisited = new Set(visited);
|
|
17
|
+
nextVisited.add(file);
|
|
18
|
+
|
|
19
|
+
let maxDepth = depth;
|
|
20
|
+
for (const dep of dependencies) {
|
|
21
|
+
maxDepth = Math.max(
|
|
22
|
+
maxDepth,
|
|
23
|
+
calculateImportDepthFromEdges(dep, edges, nextVisited, depth + 1)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return maxDepth;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getTransitiveDependenciesFromEdges(
|
|
31
|
+
file: string,
|
|
32
|
+
edges: Map<string, Set<string>>,
|
|
33
|
+
visited = new Set<string>()
|
|
34
|
+
): string[] {
|
|
35
|
+
if (visited.has(file)) return [];
|
|
36
|
+
|
|
37
|
+
const nextVisited = new Set(visited);
|
|
38
|
+
nextVisited.add(file);
|
|
39
|
+
|
|
40
|
+
const dependencies = edges.get(file);
|
|
41
|
+
if (!dependencies || dependencies.size === 0) return [];
|
|
42
|
+
|
|
43
|
+
const allDeps: string[] = [];
|
|
44
|
+
for (const dep of dependencies) {
|
|
45
|
+
allDeps.push(dep);
|
|
46
|
+
allDeps.push(
|
|
47
|
+
...getTransitiveDependenciesFromEdges(dep, edges, nextVisited)
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return [...new Set(allDeps)];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function detectGraphCycles(edges: Map<string, Set<string>>): string[][] {
|
|
55
|
+
const cycles: string[][] = [];
|
|
56
|
+
const visited = new Set<string>();
|
|
57
|
+
const recursionStack = new Set<string>();
|
|
58
|
+
|
|
59
|
+
function dfs(file: string, path: string[]): void {
|
|
60
|
+
if (recursionStack.has(file)) {
|
|
61
|
+
const cycleStart = path.indexOf(file);
|
|
62
|
+
if (cycleStart !== -1) {
|
|
63
|
+
cycles.push([...path.slice(cycleStart), file]);
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (visited.has(file)) return;
|
|
69
|
+
|
|
70
|
+
visited.add(file);
|
|
71
|
+
recursionStack.add(file);
|
|
72
|
+
|
|
73
|
+
const dependencies = edges.get(file);
|
|
74
|
+
if (dependencies) {
|
|
75
|
+
for (const dep of dependencies) {
|
|
76
|
+
dfs(dep, [...path, file]);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
recursionStack.delete(file);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (const file of edges.keys()) {
|
|
84
|
+
if (!visited.has(file)) {
|
|
85
|
+
dfs(file, []);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return cycles;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function detectGraphCyclesFromFile(
|
|
93
|
+
file: string,
|
|
94
|
+
edges: Map<string, Set<string>>
|
|
95
|
+
): string[][] {
|
|
96
|
+
const cycles: string[][] = [];
|
|
97
|
+
const visited = new Set<string>();
|
|
98
|
+
const recursionStack = new Set<string>();
|
|
99
|
+
|
|
100
|
+
function dfs(current: string, path: string[]): void {
|
|
101
|
+
if (recursionStack.has(current)) {
|
|
102
|
+
const cycleStart = path.indexOf(current);
|
|
103
|
+
if (cycleStart !== -1) {
|
|
104
|
+
cycles.push([...path.slice(cycleStart), current]);
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (visited.has(current)) return;
|
|
110
|
+
|
|
111
|
+
visited.add(current);
|
|
112
|
+
recursionStack.add(current);
|
|
113
|
+
|
|
114
|
+
const dependencies = edges.get(current);
|
|
115
|
+
if (dependencies) {
|
|
116
|
+
for (const dep of dependencies) {
|
|
117
|
+
dfs(dep, [...path, current]);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
recursionStack.delete(current);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
dfs(file, []);
|
|
125
|
+
return cycles;
|
|
126
|
+
}
|