@aiready/context-analyzer 0.21.13 → 0.21.15
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 +10 -10
- package/.turbo/turbo-test.log +26 -25
- package/dist/chunk-BEZPBI5C.mjs +1829 -0
- package/dist/cli.js +28 -0
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +28 -0
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
- package/src/__tests__/boilerplate-barrel.test.ts +103 -0
- package/src/classifier.ts +12 -1
- package/src/heuristics.ts +35 -1
- package/src/remediation.ts +6 -0
- package/src/types.ts +1 -0
package/dist/cli.js
CHANGED
|
@@ -923,7 +923,19 @@ var CONFIG_NAME_PATTERNS = [
|
|
|
923
923
|
"next.config",
|
|
924
924
|
"sst.config"
|
|
925
925
|
];
|
|
926
|
+
function isBoilerplateBarrel(node) {
|
|
927
|
+
const { exports: exports2, tokenCost } = node;
|
|
928
|
+
if (!exports2 || exports2.length === 0) return false;
|
|
929
|
+
const isPurelyReexports = exports2.every((exp) => !!exp.source);
|
|
930
|
+
if (!isPurelyReexports) return false;
|
|
931
|
+
if (tokenCost > 500) return false;
|
|
932
|
+
const sources = new Set(exports2.map((exp) => exp.source));
|
|
933
|
+
const isSingleSourcePassThrough = sources.size === 1;
|
|
934
|
+
const isMeaninglessAggregation = sources.size > 0 && sources.size < 3;
|
|
935
|
+
return isSingleSourcePassThrough || isMeaninglessAggregation;
|
|
936
|
+
}
|
|
926
937
|
function isBarrelExport(node) {
|
|
938
|
+
if (isBoilerplateBarrel(node)) return false;
|
|
927
939
|
const { file, exports: exports2 } = node;
|
|
928
940
|
const fileName = file.split("/").pop()?.toLowerCase();
|
|
929
941
|
const isIndexFile = fileName === "index.ts" || fileName === "index.js";
|
|
@@ -1054,6 +1066,7 @@ function isHubAndSpokeFile(node) {
|
|
|
1054
1066
|
// src/classifier.ts
|
|
1055
1067
|
var Classification = {
|
|
1056
1068
|
BARREL: "barrel-export",
|
|
1069
|
+
BOILERPLATE: "boilerplate-barrel",
|
|
1057
1070
|
TYPE_DEFINITION: "type-definition",
|
|
1058
1071
|
NEXTJS_PAGE: "nextjs-page",
|
|
1059
1072
|
LAMBDA_HANDLER: "lambda-handler",
|
|
@@ -1067,6 +1080,9 @@ var Classification = {
|
|
|
1067
1080
|
UNKNOWN: "unknown"
|
|
1068
1081
|
};
|
|
1069
1082
|
function classifyFile(node, cohesionScore = 1, domains = []) {
|
|
1083
|
+
if (isBoilerplateBarrel(node)) {
|
|
1084
|
+
return Classification.BOILERPLATE;
|
|
1085
|
+
}
|
|
1070
1086
|
if (isBarrelExport(node)) {
|
|
1071
1087
|
return Classification.BARREL;
|
|
1072
1088
|
}
|
|
@@ -1115,6 +1131,9 @@ function classifyFile(node, cohesionScore = 1, domains = []) {
|
|
|
1115
1131
|
}
|
|
1116
1132
|
function adjustCohesionForClassification(baseCohesion, classification, node) {
|
|
1117
1133
|
switch (classification) {
|
|
1134
|
+
case Classification.BOILERPLATE:
|
|
1135
|
+
return 0.2;
|
|
1136
|
+
// Redundant indirection is low cohesion (architectural theater)
|
|
1118
1137
|
case Classification.BARREL:
|
|
1119
1138
|
return 1;
|
|
1120
1139
|
case Classification.TYPE_DEFINITION:
|
|
@@ -1190,6 +1209,9 @@ function hasRelatedExportNames(exportNames) {
|
|
|
1190
1209
|
}
|
|
1191
1210
|
function adjustFragmentationForClassification(baseFragmentation, classification) {
|
|
1192
1211
|
switch (classification) {
|
|
1212
|
+
case Classification.BOILERPLATE:
|
|
1213
|
+
return baseFragmentation * 1.5;
|
|
1214
|
+
// Redundant barrels increase fragmentation
|
|
1193
1215
|
case Classification.BARREL:
|
|
1194
1216
|
return 0;
|
|
1195
1217
|
case Classification.TYPE_DEFINITION:
|
|
@@ -1298,6 +1320,12 @@ function detectModuleClusters(graph, options) {
|
|
|
1298
1320
|
// src/remediation.ts
|
|
1299
1321
|
function getClassificationRecommendations(classification, file, issues) {
|
|
1300
1322
|
switch (classification) {
|
|
1323
|
+
case "boilerplate-barrel":
|
|
1324
|
+
return [
|
|
1325
|
+
"Redundant indirection detected (architectural theater)",
|
|
1326
|
+
"Remove this pass-through barrel export to reduce cognitive load",
|
|
1327
|
+
"Consider combining into meaningful domain exports if necessary"
|
|
1328
|
+
];
|
|
1301
1329
|
case "barrel-export":
|
|
1302
1330
|
return [
|
|
1303
1331
|
"Barrel export file detected - multiple domains are expected here",
|
package/dist/cli.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -69,7 +69,7 @@ interface ContextAnalysisResult {
|
|
|
69
69
|
* Classification of file type for analysis context
|
|
70
70
|
* Helps distinguish real issues from false positives
|
|
71
71
|
*/
|
|
72
|
-
type FileClassification = 'barrel-export' | 'type-definition' | 'cohesive-module' | 'utility-module' | 'service-file' | 'lambda-handler' | 'email-template' | 'parser-file' | 'nextjs-page' | 'spoke-module' | 'mixed-concerns' | 'unknown';
|
|
72
|
+
type FileClassification = 'barrel-export' | 'boilerplate-barrel' | 'type-definition' | 'cohesive-module' | 'utility-module' | 'service-file' | 'lambda-handler' | 'email-template' | 'parser-file' | 'nextjs-page' | 'spoke-module' | 'mixed-concerns' | 'unknown';
|
|
73
73
|
interface ModuleCluster {
|
|
74
74
|
domain: string;
|
|
75
75
|
files: string[];
|
|
@@ -292,6 +292,7 @@ declare function calculateDirectoryDistance(files: string[]): number;
|
|
|
292
292
|
*/
|
|
293
293
|
declare const Classification: {
|
|
294
294
|
BARREL: "barrel-export";
|
|
295
|
+
BOILERPLATE: "boilerplate-barrel";
|
|
295
296
|
TYPE_DEFINITION: "type-definition";
|
|
296
297
|
NEXTJS_PAGE: "nextjs-page";
|
|
297
298
|
LAMBDA_HANDLER: "lambda-handler";
|
package/dist/index.d.ts
CHANGED
|
@@ -69,7 +69,7 @@ interface ContextAnalysisResult {
|
|
|
69
69
|
* Classification of file type for analysis context
|
|
70
70
|
* Helps distinguish real issues from false positives
|
|
71
71
|
*/
|
|
72
|
-
type FileClassification = 'barrel-export' | 'type-definition' | 'cohesive-module' | 'utility-module' | 'service-file' | 'lambda-handler' | 'email-template' | 'parser-file' | 'nextjs-page' | 'spoke-module' | 'mixed-concerns' | 'unknown';
|
|
72
|
+
type FileClassification = 'barrel-export' | 'boilerplate-barrel' | 'type-definition' | 'cohesive-module' | 'utility-module' | 'service-file' | 'lambda-handler' | 'email-template' | 'parser-file' | 'nextjs-page' | 'spoke-module' | 'mixed-concerns' | 'unknown';
|
|
73
73
|
interface ModuleCluster {
|
|
74
74
|
domain: string;
|
|
75
75
|
files: string[];
|
|
@@ -292,6 +292,7 @@ declare function calculateDirectoryDistance(files: string[]): number;
|
|
|
292
292
|
*/
|
|
293
293
|
declare const Classification: {
|
|
294
294
|
BARREL: "barrel-export";
|
|
295
|
+
BOILERPLATE: "boilerplate-barrel";
|
|
295
296
|
TYPE_DEFINITION: "type-definition";
|
|
296
297
|
NEXTJS_PAGE: "nextjs-page";
|
|
297
298
|
LAMBDA_HANDLER: "lambda-handler";
|
package/dist/index.js
CHANGED
|
@@ -1057,7 +1057,19 @@ var CONFIG_NAME_PATTERNS = [
|
|
|
1057
1057
|
"next.config",
|
|
1058
1058
|
"sst.config"
|
|
1059
1059
|
];
|
|
1060
|
+
function isBoilerplateBarrel(node) {
|
|
1061
|
+
const { exports: exports2, tokenCost } = node;
|
|
1062
|
+
if (!exports2 || exports2.length === 0) return false;
|
|
1063
|
+
const isPurelyReexports = exports2.every((exp) => !!exp.source);
|
|
1064
|
+
if (!isPurelyReexports) return false;
|
|
1065
|
+
if (tokenCost > 500) return false;
|
|
1066
|
+
const sources = new Set(exports2.map((exp) => exp.source));
|
|
1067
|
+
const isSingleSourcePassThrough = sources.size === 1;
|
|
1068
|
+
const isMeaninglessAggregation = sources.size > 0 && sources.size < 3;
|
|
1069
|
+
return isSingleSourcePassThrough || isMeaninglessAggregation;
|
|
1070
|
+
}
|
|
1060
1071
|
function isBarrelExport(node) {
|
|
1072
|
+
if (isBoilerplateBarrel(node)) return false;
|
|
1061
1073
|
const { file, exports: exports2 } = node;
|
|
1062
1074
|
const fileName = file.split("/").pop()?.toLowerCase();
|
|
1063
1075
|
const isIndexFile = fileName === "index.ts" || fileName === "index.js";
|
|
@@ -1188,6 +1200,7 @@ function isHubAndSpokeFile(node) {
|
|
|
1188
1200
|
// src/classifier.ts
|
|
1189
1201
|
var Classification = {
|
|
1190
1202
|
BARREL: "barrel-export",
|
|
1203
|
+
BOILERPLATE: "boilerplate-barrel",
|
|
1191
1204
|
TYPE_DEFINITION: "type-definition",
|
|
1192
1205
|
NEXTJS_PAGE: "nextjs-page",
|
|
1193
1206
|
LAMBDA_HANDLER: "lambda-handler",
|
|
@@ -1201,6 +1214,9 @@ var Classification = {
|
|
|
1201
1214
|
UNKNOWN: "unknown"
|
|
1202
1215
|
};
|
|
1203
1216
|
function classifyFile(node, cohesionScore = 1, domains = []) {
|
|
1217
|
+
if (isBoilerplateBarrel(node)) {
|
|
1218
|
+
return Classification.BOILERPLATE;
|
|
1219
|
+
}
|
|
1204
1220
|
if (isBarrelExport(node)) {
|
|
1205
1221
|
return Classification.BARREL;
|
|
1206
1222
|
}
|
|
@@ -1249,6 +1265,9 @@ function classifyFile(node, cohesionScore = 1, domains = []) {
|
|
|
1249
1265
|
}
|
|
1250
1266
|
function adjustCohesionForClassification(baseCohesion, classification, node) {
|
|
1251
1267
|
switch (classification) {
|
|
1268
|
+
case Classification.BOILERPLATE:
|
|
1269
|
+
return 0.2;
|
|
1270
|
+
// Redundant indirection is low cohesion (architectural theater)
|
|
1252
1271
|
case Classification.BARREL:
|
|
1253
1272
|
return 1;
|
|
1254
1273
|
case Classification.TYPE_DEFINITION:
|
|
@@ -1324,6 +1343,9 @@ function hasRelatedExportNames(exportNames) {
|
|
|
1324
1343
|
}
|
|
1325
1344
|
function adjustFragmentationForClassification(baseFragmentation, classification) {
|
|
1326
1345
|
switch (classification) {
|
|
1346
|
+
case Classification.BOILERPLATE:
|
|
1347
|
+
return baseFragmentation * 1.5;
|
|
1348
|
+
// Redundant barrels increase fragmentation
|
|
1327
1349
|
case Classification.BARREL:
|
|
1328
1350
|
return 0;
|
|
1329
1351
|
case Classification.TYPE_DEFINITION:
|
|
@@ -1432,6 +1454,12 @@ function detectModuleClusters(graph, options) {
|
|
|
1432
1454
|
// src/remediation.ts
|
|
1433
1455
|
function getClassificationRecommendations(classification, file, issues) {
|
|
1434
1456
|
switch (classification) {
|
|
1457
|
+
case "boilerplate-barrel":
|
|
1458
|
+
return [
|
|
1459
|
+
"Redundant indirection detected (architectural theater)",
|
|
1460
|
+
"Remove this pass-through barrel export to reduce cognitive load",
|
|
1461
|
+
"Consider combining into meaningful domain exports if necessary"
|
|
1462
|
+
];
|
|
1435
1463
|
case "barrel-export":
|
|
1436
1464
|
return [
|
|
1437
1465
|
"Barrel export file detected - multiple domains are expected here",
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/context-analyzer",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.15",
|
|
4
4
|
"description": "AI context window cost analysis - detect fragmented code, deep import chains, and expensive context budgets",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"commander": "^14.0.0",
|
|
50
50
|
"chalk": "^5.3.0",
|
|
51
51
|
"prompts": "^2.4.2",
|
|
52
|
-
"@aiready/core": "0.23.
|
|
52
|
+
"@aiready/core": "0.23.12"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/node": "^24.0.0",
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
classifyFile,
|
|
4
|
+
adjustFragmentationForClassification,
|
|
5
|
+
adjustCohesionForClassification,
|
|
6
|
+
getClassificationRecommendations,
|
|
7
|
+
} from '../index';
|
|
8
|
+
import type { DependencyNode } from '../types';
|
|
9
|
+
|
|
10
|
+
describe('boilerplate-barrel classification', () => {
|
|
11
|
+
const createNode = (overrides: Partial<DependencyNode>): DependencyNode => ({
|
|
12
|
+
file: 'test.ts',
|
|
13
|
+
imports: [],
|
|
14
|
+
exports: [],
|
|
15
|
+
tokenCost: 100,
|
|
16
|
+
linesOfCode: 50,
|
|
17
|
+
...overrides,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should classify single source pass-through as boilerplate-barrel', () => {
|
|
21
|
+
const node = createNode({
|
|
22
|
+
file: 'core/lib/types/domains/agent-types.ts',
|
|
23
|
+
tokenCost: 50,
|
|
24
|
+
exports: [{ name: '*', type: 'all', source: '../agent' } as any],
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const classification = classifyFile(node, 1.0, ['agent']);
|
|
28
|
+
expect(classification).toBe('boilerplate-barrel');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should classify meaningless aggregation (2 sources) as boilerplate-barrel', () => {
|
|
32
|
+
const node = createNode({
|
|
33
|
+
file: 'src/types/mini-barrel.ts',
|
|
34
|
+
tokenCost: 80,
|
|
35
|
+
exports: [
|
|
36
|
+
{ name: 'User', type: 'type', source: './user' } as any,
|
|
37
|
+
{ name: 'Profile', type: 'type', source: './profile' } as any,
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const classification = classifyFile(node, 1.0, ['user', 'profile']);
|
|
42
|
+
expect(classification).toBe('boilerplate-barrel');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should classify meaningful aggregation (5+ sources) as regular barrel-export', () => {
|
|
46
|
+
const node = createNode({
|
|
47
|
+
file: 'src/index.ts',
|
|
48
|
+
tokenCost: 150,
|
|
49
|
+
exports: [
|
|
50
|
+
{ name: 'A', type: 'const', source: './a' } as any,
|
|
51
|
+
{ name: 'B', type: 'const', source: './b' } as any,
|
|
52
|
+
{ name: 'C', type: 'const', source: './c' } as any,
|
|
53
|
+
{ name: 'D', type: 'const', source: './d' } as any,
|
|
54
|
+
{ name: 'E', type: 'const', source: './e' } as any,
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const classification = classifyFile(node, 1.0, ['a', 'b', 'c', 'd', 'e']);
|
|
59
|
+
expect(classification).toBe('barrel-export');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should NOT classify barrel with local logic as boilerplate', () => {
|
|
63
|
+
const node = createNode({
|
|
64
|
+
file: 'src/mixed-barrel.ts',
|
|
65
|
+
tokenCost: 600, // Above limit
|
|
66
|
+
exports: [
|
|
67
|
+
{ name: 'A', type: 'const', source: './a' } as any,
|
|
68
|
+
{ name: 'localFunc', type: 'function' } as any,
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Should default to something else, or if it matches barrel patterns but has local logic
|
|
73
|
+
const classification = classifyFile(node, 0.5, ['a', 'local']);
|
|
74
|
+
expect(classification).not.toBe('boilerplate-barrel');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should penalize boilerplate-barrel in cohesion adjustment', () => {
|
|
78
|
+
const result = adjustCohesionForClassification(1.0, 'boilerplate-barrel');
|
|
79
|
+
expect(result).toBe(0.2); // Low score for architectural theater
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should increase fragmentation for boilerplate-barrel', () => {
|
|
83
|
+
const result = adjustFragmentationForClassification(
|
|
84
|
+
0.4,
|
|
85
|
+
'boilerplate-barrel'
|
|
86
|
+
);
|
|
87
|
+
expect(result).toBeCloseTo(0.6, 2); // 0.4 * 1.5
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should provide specific recommendations for boilerplate-barrel', () => {
|
|
91
|
+
const recommendations = getClassificationRecommendations(
|
|
92
|
+
'boilerplate-barrel',
|
|
93
|
+
'types/domains/agent-types.ts',
|
|
94
|
+
[]
|
|
95
|
+
);
|
|
96
|
+
expect(recommendations).toContain(
|
|
97
|
+
'Redundant indirection detected (architectural theater)'
|
|
98
|
+
);
|
|
99
|
+
expect(recommendations).toContain(
|
|
100
|
+
'Remove this pass-through barrel export to reduce cognitive load'
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
});
|
package/src/classifier.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { DependencyNode, FileClassification } from './types';
|
|
2
2
|
import {
|
|
3
3
|
isBarrelExport,
|
|
4
|
+
isBoilerplateBarrel,
|
|
4
5
|
isTypeDefinition,
|
|
5
6
|
isNextJsPage,
|
|
6
7
|
isLambdaHandler,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
*/
|
|
19
20
|
export const Classification = {
|
|
20
21
|
BARREL: 'barrel-export' as const,
|
|
22
|
+
BOILERPLATE: 'boilerplate-barrel' as const,
|
|
21
23
|
TYPE_DEFINITION: 'type-definition' as const,
|
|
22
24
|
NEXTJS_PAGE: 'nextjs-page' as const,
|
|
23
25
|
LAMBDA_HANDLER: 'lambda-handler' as const,
|
|
@@ -44,7 +46,12 @@ export function classifyFile(
|
|
|
44
46
|
cohesionScore: number = 1,
|
|
45
47
|
domains: string[] = []
|
|
46
48
|
): FileClassification {
|
|
47
|
-
// 1. Detect
|
|
49
|
+
// 1. Detect boilerplate barrels (pure indirection/architectural theater)
|
|
50
|
+
if (isBoilerplateBarrel(node)) {
|
|
51
|
+
return Classification.BOILERPLATE;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 2. Detect legitimate barrel exports (primarily re-exports that aggregate)
|
|
48
55
|
if (isBarrelExport(node)) {
|
|
49
56
|
return Classification.BARREL;
|
|
50
57
|
}
|
|
@@ -134,6 +141,8 @@ export function adjustCohesionForClassification(
|
|
|
134
141
|
node?: DependencyNode
|
|
135
142
|
): number {
|
|
136
143
|
switch (classification) {
|
|
144
|
+
case Classification.BOILERPLATE:
|
|
145
|
+
return 0.2; // Redundant indirection is low cohesion (architectural theater)
|
|
137
146
|
case Classification.BARREL:
|
|
138
147
|
return 1;
|
|
139
148
|
case Classification.TYPE_DEFINITION:
|
|
@@ -235,6 +244,8 @@ export function adjustFragmentationForClassification(
|
|
|
235
244
|
classification: FileClassification
|
|
236
245
|
): number {
|
|
237
246
|
switch (classification) {
|
|
247
|
+
case Classification.BOILERPLATE:
|
|
248
|
+
return baseFragmentation * 1.5; // Redundant barrels increase fragmentation
|
|
238
249
|
case Classification.BARREL:
|
|
239
250
|
return 0;
|
|
240
251
|
case Classification.TYPE_DEFINITION:
|
package/src/heuristics.ts
CHANGED
|
@@ -64,14 +64,48 @@ const CONFIG_NAME_PATTERNS = [
|
|
|
64
64
|
'sst.config',
|
|
65
65
|
];
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Detect if a file is a boilerplate barrel (architectural theater).
|
|
69
|
+
* A boilerplate barrel re-exports from other sources without adding value.
|
|
70
|
+
*
|
|
71
|
+
* @param node - The dependency node to analyze.
|
|
72
|
+
* @returns True if the file matches boilerplate barrel patterns.
|
|
73
|
+
*/
|
|
74
|
+
export function isBoilerplateBarrel(node: DependencyNode): boolean {
|
|
75
|
+
const { exports, tokenCost } = node;
|
|
76
|
+
if (!exports || exports.length === 0) return false;
|
|
77
|
+
|
|
78
|
+
// 1. Must be purely re-exports
|
|
79
|
+
const isPurelyReexports = exports.every((exp: any) => !!exp.source);
|
|
80
|
+
if (!isPurelyReexports) return false;
|
|
81
|
+
|
|
82
|
+
// 2. Must be low local token cost (no actual logic)
|
|
83
|
+
if (tokenCost > 500) return false;
|
|
84
|
+
|
|
85
|
+
// 3. Detect "Architectural Theater"
|
|
86
|
+
// If it re-exports everything from exactly ONE source, it's a pass-through
|
|
87
|
+
const sources = new Set(exports.map((exp: any) => exp.source));
|
|
88
|
+
|
|
89
|
+
// Pattern: export * from '../actual'
|
|
90
|
+
const isSingleSourcePassThrough = sources.size === 1;
|
|
91
|
+
|
|
92
|
+
// Pattern: multi-file 1-to-1 re-exports that don't aggregate much
|
|
93
|
+
// (e.g., 6 files each re-exporting from exactly one other file)
|
|
94
|
+
const isMeaninglessAggregation = sources.size > 0 && sources.size < 3;
|
|
95
|
+
|
|
96
|
+
return isSingleSourcePassThrough || isMeaninglessAggregation;
|
|
97
|
+
}
|
|
98
|
+
|
|
67
99
|
/**
|
|
68
100
|
* Detect if a file is a barrel export (index.ts).
|
|
69
101
|
*
|
|
70
102
|
* @param node - The dependency node to analyze.
|
|
71
103
|
* @returns True if the file matches barrel export patterns.
|
|
72
|
-
* @lastUpdated 2026-03-
|
|
104
|
+
* @lastUpdated 2026-03-20
|
|
73
105
|
*/
|
|
74
106
|
export function isBarrelExport(node: DependencyNode): boolean {
|
|
107
|
+
if (isBoilerplateBarrel(node)) return false; // Already handled by more specific check
|
|
108
|
+
|
|
75
109
|
const { file, exports } = node;
|
|
76
110
|
const fileName = file.split('/').pop()?.toLowerCase();
|
|
77
111
|
|
package/src/remediation.ts
CHANGED
|
@@ -14,6 +14,12 @@ export function getClassificationRecommendations(
|
|
|
14
14
|
issues: string[]
|
|
15
15
|
): string[] {
|
|
16
16
|
switch (classification) {
|
|
17
|
+
case 'boilerplate-barrel':
|
|
18
|
+
return [
|
|
19
|
+
'Redundant indirection detected (architectural theater)',
|
|
20
|
+
'Remove this pass-through barrel export to reduce cognitive load',
|
|
21
|
+
'Consider combining into meaningful domain exports if necessary',
|
|
22
|
+
];
|
|
17
23
|
case 'barrel-export':
|
|
18
24
|
return [
|
|
19
25
|
'Barrel export file detected - multiple domains are expected here',
|
package/src/types.ts
CHANGED
|
@@ -80,6 +80,7 @@ export interface ContextAnalysisResult {
|
|
|
80
80
|
*/
|
|
81
81
|
export type FileClassification =
|
|
82
82
|
| 'barrel-export' // Re-exports from other modules (index.ts files)
|
|
83
|
+
| 'boilerplate-barrel' // Redundant barrel file with no added value (architectural theater)
|
|
83
84
|
| 'type-definition' // Primarily type/interface definitions
|
|
84
85
|
| 'cohesive-module' // Single domain, high cohesion (acceptable large files)
|
|
85
86
|
| 'utility-module' // Utility/helper files with cohesive purpose despite multi-domain
|