@aiready/context-analyzer 0.9.22 → 0.9.25

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.
@@ -0,0 +1,251 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ classifyFile,
4
+ adjustFragmentationForClassification,
5
+ getClassificationRecommendations,
6
+ } from '../analyzer';
7
+ import type { DependencyNode, FileClassification } from '../types';
8
+
9
+ describe('file classification', () => {
10
+ const createNode = (overrides: Partial<DependencyNode>): DependencyNode => ({
11
+ file: 'test.ts',
12
+ imports: [],
13
+ exports: [],
14
+ tokenCost: 100,
15
+ linesOfCode: 50,
16
+ ...overrides,
17
+ });
18
+
19
+ describe('classifyFile', () => {
20
+ it('should classify barrel export files correctly', () => {
21
+ const node = createNode({
22
+ file: 'src/index.ts',
23
+ imports: ['../module1', '../module2', '../module3'],
24
+ exports: [
25
+ { name: 'func1', type: 'function', inferredDomain: 'module1' },
26
+ { name: 'func2', type: 'function', inferredDomain: 'module2' },
27
+ { name: 'func3', type: 'function', inferredDomain: 'module3' },
28
+ ],
29
+ linesOfCode: 20, // Sparse code
30
+ });
31
+
32
+ const classification = classifyFile(node, 0.5, ['module1', 'module2', 'module3']);
33
+ expect(classification).toBe('barrel-export');
34
+ });
35
+
36
+ it('should classify type definition files correctly', () => {
37
+ const node = createNode({
38
+ file: 'src/types.ts',
39
+ exports: [
40
+ { name: 'User', type: 'interface', inferredDomain: 'user' },
41
+ { name: 'Order', type: 'interface', inferredDomain: 'order' },
42
+ { name: 'Product', type: 'type', inferredDomain: 'product' },
43
+ { name: 'Status', type: 'type', inferredDomain: 'unknown' },
44
+ ],
45
+ linesOfCode: 100,
46
+ });
47
+
48
+ const classification = classifyFile(node, 0.5, ['user', 'order', 'product']);
49
+ expect(classification).toBe('type-definition');
50
+ });
51
+
52
+ it('should classify cohesive module files correctly', () => {
53
+ const node = createNode({
54
+ file: 'src/calculator.ts',
55
+ exports: [
56
+ { name: 'calculate', type: 'function', inferredDomain: 'calc' },
57
+ { name: 'format', type: 'function', inferredDomain: 'calc' },
58
+ { name: 'validate', type: 'function', inferredDomain: 'calc' },
59
+ ],
60
+ imports: ['../utils'],
61
+ linesOfCode: 300,
62
+ });
63
+
64
+ const classification = classifyFile(node, 0.8, ['calc']);
65
+ expect(classification).toBe('cohesive-module');
66
+ });
67
+
68
+ it('should classify mixed concerns files correctly', () => {
69
+ const node = createNode({
70
+ file: 'src/audit.ts',
71
+ exports: [
72
+ { name: 'auditStatus', type: 'function', inferredDomain: 'audit' },
73
+ { name: 'createJob', type: 'function', inferredDomain: 'job' },
74
+ { name: 'LineItem', type: 'interface', inferredDomain: 'order' },
75
+ { name: 'SupportingDoc', type: 'type', inferredDomain: 'doc' },
76
+ ],
77
+ imports: ['../auth', '../job', '../order'],
78
+ linesOfCode: 384,
79
+ });
80
+
81
+ const classification = classifyFile(node, 0.3, ['audit', 'job', 'order', 'doc']);
82
+ expect(classification).toBe('mixed-concerns');
83
+ });
84
+
85
+ it('should classify files with multiple domains and very low cohesion as mixed-concerns', () => {
86
+ const node = createNode({
87
+ file: 'src/services/mixed-service.ts', // NOT a utility/config path
88
+ exports: [
89
+ { name: 'DateFormatter', type: 'class', inferredDomain: 'date' }, // Use class to avoid utility detection
90
+ { name: 'JSONParser', type: 'class', inferredDomain: 'json' },
91
+ { name: 'EmailValidator', type: 'class', inferredDomain: 'email' },
92
+ ],
93
+ imports: [],
94
+ linesOfCode: 150,
95
+ });
96
+
97
+ // Multiple domains + very low cohesion (< 0.4) = mixed concerns
98
+ // Note: NOT in /utils/ or /helpers/ path, uses classes (not just functions/consts)
99
+ const classification = classifyFile(node, 0.3, ['date', 'json', 'email']);
100
+ expect(classification).toBe('mixed-concerns');
101
+ });
102
+
103
+ it('should classify single domain files as cohesive-module regardless of cohesion', () => {
104
+ const node = createNode({
105
+ file: 'src/component.ts',
106
+ exports: [
107
+ { name: 'Component', type: 'function', inferredDomain: 'ui' },
108
+ ],
109
+ imports: ['react'],
110
+ linesOfCode: 100,
111
+ });
112
+
113
+ // Single domain = cohesive module (even with medium cohesion)
114
+ const classification = classifyFile(node, 0.6, ['ui']);
115
+ expect(classification).toBe('cohesive-module');
116
+ });
117
+
118
+ it('should classify utility files as cohesive-module by design', () => {
119
+ const node = createNode({
120
+ file: 'src/utils/helpers.ts',
121
+ exports: [
122
+ { name: 'formatDate', type: 'function', inferredDomain: 'date' },
123
+ { name: 'parseJSON', type: 'function', inferredDomain: 'json' },
124
+ { name: 'validateEmail', type: 'function', inferredDomain: 'email' },
125
+ ],
126
+ imports: [],
127
+ linesOfCode: 150,
128
+ });
129
+
130
+ // Utility files are classified as cohesive by design
131
+ const classification = classifyFile(node, 0.4, ['date', 'json', 'email']);
132
+ expect(classification).toBe('cohesive-module');
133
+ });
134
+
135
+ it('should classify config/schema files as cohesive-module', () => {
136
+ const node = createNode({
137
+ file: 'src/db-schema.ts',
138
+ exports: [
139
+ { name: 'userTable', type: 'const', inferredDomain: 'db' },
140
+ { name: 'userSchema', type: 'const', inferredDomain: 'schema' },
141
+ ],
142
+ imports: ['../db'],
143
+ linesOfCode: 81,
144
+ });
145
+
146
+ // Config/schema files are classified as cohesive
147
+ const classification = classifyFile(node, 0.4, ['db', 'schema']);
148
+ expect(classification).toBe('cohesive-module');
149
+ });
150
+ });
151
+
152
+ describe('adjustFragmentationForClassification', () => {
153
+ it('should return 0 fragmentation for barrel exports', () => {
154
+ const result = adjustFragmentationForClassification(0.8, 'barrel-export');
155
+ expect(result).toBe(0);
156
+ });
157
+
158
+ it('should return 0 fragmentation for type definitions', () => {
159
+ const result = adjustFragmentationForClassification(0.9, 'type-definition');
160
+ expect(result).toBe(0);
161
+ });
162
+
163
+ it('should reduce fragmentation by 70% for cohesive modules', () => {
164
+ const result = adjustFragmentationForClassification(0.6, 'cohesive-module');
165
+ expect(result).toBeCloseTo(0.18, 2); // 0.6 * 0.3
166
+ });
167
+
168
+ it('should keep full fragmentation for mixed concerns', () => {
169
+ const result = adjustFragmentationForClassification(0.7, 'mixed-concerns');
170
+ expect(result).toBe(0.7);
171
+ });
172
+
173
+ it('should reduce fragmentation by 30% for unknown classification', () => {
174
+ const result = adjustFragmentationForClassification(0.5, 'unknown');
175
+ expect(result).toBeCloseTo(0.35, 2); // 0.5 * 0.7
176
+ });
177
+ });
178
+
179
+ describe('getClassificationRecommendations', () => {
180
+ it('should provide barrel export recommendations', () => {
181
+ const recommendations = getClassificationRecommendations(
182
+ 'barrel-export',
183
+ 'src/index.ts',
184
+ ['High fragmentation']
185
+ );
186
+ expect(recommendations).toContain('Barrel export file detected - multiple domains are expected here');
187
+ });
188
+
189
+ it('should provide type definition recommendations', () => {
190
+ const recommendations = getClassificationRecommendations(
191
+ 'type-definition',
192
+ 'src/types.ts',
193
+ ['High fragmentation']
194
+ );
195
+ expect(recommendations).toContain('Type definition file - centralized types improve consistency');
196
+ });
197
+
198
+ it('should provide cohesive module recommendations', () => {
199
+ const recommendations = getClassificationRecommendations(
200
+ 'cohesive-module',
201
+ 'src/calculator.ts',
202
+ []
203
+ );
204
+ expect(recommendations).toContain('Module has good cohesion despite its size');
205
+ });
206
+
207
+ it('should provide mixed concerns recommendations', () => {
208
+ const recommendations = getClassificationRecommendations(
209
+ 'mixed-concerns',
210
+ 'src/audit.ts',
211
+ ['Multiple domains detected']
212
+ );
213
+ expect(recommendations).toContain('Consider splitting this file by domain');
214
+ });
215
+ });
216
+
217
+ describe('integration: barrel export detection edge cases', () => {
218
+ it('should detect barrel export even for non-index files with re-export patterns', () => {
219
+ const node = createNode({
220
+ file: 'src/exports.ts',
221
+ imports: ['../module1', '../module2', '../module3', '../module4', '../module5'],
222
+ exports: [
223
+ { name: 'a', type: 'function' },
224
+ { name: 'b', type: 'function' },
225
+ { name: 'c', type: 'function' },
226
+ { name: 'd', type: 'function' },
227
+ { name: 'e', type: 'function' },
228
+ ],
229
+ linesOfCode: 25, // Very sparse - mostly re-exports
230
+ });
231
+
232
+ const classification = classifyFile(node, 0.5, ['module1', 'module2']);
233
+ expect(classification).toBe('barrel-export');
234
+ });
235
+
236
+ it('should not misclassify large component files as barrel exports', () => {
237
+ const node = createNode({
238
+ file: 'src/components/Calculator.tsx', // NOT an index file
239
+ imports: ['react', '../hooks', '../utils'],
240
+ exports: [
241
+ { name: 'Calculator', type: 'function' },
242
+ ],
243
+ linesOfCode: 346, // Substantial code
244
+ });
245
+
246
+ // Single domain, high cohesion
247
+ const classification = classifyFile(node, 0.9, ['calculator']);
248
+ expect(classification).toBe('cohesive-module');
249
+ });
250
+ });
251
+ });
package/src/analyzer.ts CHANGED
@@ -5,6 +5,7 @@ import type {
5
5
  DependencyNode,
6
6
  ExportInfo,
7
7
  ModuleCluster,
8
+ FileClassification,
8
9
  } from './types';
9
10
  import { buildCoUsageMatrix, buildTypeGraph, inferDomainFromSemantics } from './semantic-analysis';
10
11
 
@@ -901,3 +902,283 @@ function calculateDomainCohesion(exports: ExportInfo[]): number {
901
902
  const maxEntropy = Math.log2(total);
902
903
  return maxEntropy > 0 ? 1 - entropy / maxEntropy : 1;
903
904
  }
905
+
906
+ /**
907
+ * Classify a file based on its characteristics to help distinguish
908
+ * real issues from false positives.
909
+ *
910
+ * Classification types:
911
+ * - barrel-export: Re-exports from other modules (index.ts files)
912
+ * - type-definition: Primarily type/interface definitions
913
+ * - cohesive-module: Single domain, high cohesion (acceptable large files)
914
+ * - mixed-concerns: Multiple domains, potential refactoring candidate
915
+ * - unknown: Unable to classify
916
+ */
917
+ export function classifyFile(
918
+ node: DependencyNode,
919
+ cohesionScore: number,
920
+ domains: string[]
921
+ ): FileClassification {
922
+ const { exports, imports, linesOfCode, file } = node;
923
+
924
+ // 1. Check for barrel export (index file that re-exports)
925
+ if (isBarrelExport(node)) {
926
+ return 'barrel-export';
927
+ }
928
+
929
+ // 2. Check for type definition file
930
+ if (isTypeDefinitionFile(node)) {
931
+ return 'type-definition';
932
+ }
933
+
934
+ // 3. Check for config/schema file (special case - acceptable multi-domain)
935
+ if (isConfigOrSchemaFile(node)) {
936
+ return 'cohesive-module'; // Treat as cohesive since it's intentional
937
+ }
938
+
939
+ // 4. Check for cohesive module (single domain + reasonable cohesion)
940
+ const uniqueDomains = domains.filter(d => d !== 'unknown');
941
+ const hasSingleDomain = uniqueDomains.length <= 1;
942
+ const hasReasonableCohesion = cohesionScore >= 0.5; // Lowered threshold
943
+
944
+ // Single domain files are almost always cohesive (even with lower cohesion score)
945
+ if (hasSingleDomain) {
946
+ return 'cohesive-module';
947
+ }
948
+
949
+ // 5. Check for utility file pattern (multiple domains but utility purpose)
950
+ if (isUtilityFile(node)) {
951
+ return 'cohesive-module'; // Utilities often have mixed imports by design
952
+ }
953
+
954
+ // 6. Check for mixed concerns (multiple domains + low cohesion)
955
+ const hasMultipleDomains = uniqueDomains.length > 1;
956
+ const hasLowCohesion = cohesionScore < 0.4; // Lowered threshold
957
+
958
+ if (hasMultipleDomains && hasLowCohesion) {
959
+ return 'mixed-concerns';
960
+ }
961
+
962
+ // 7. Default to cohesive-module for files with reasonable cohesion
963
+ // This reduces false positives for legitimate files
964
+ if (cohesionScore >= 0.5) {
965
+ return 'cohesive-module';
966
+ }
967
+
968
+ return 'unknown';
969
+ }
970
+
971
+ /**
972
+ * Detect if a file is a barrel export (re-exports from other modules)
973
+ *
974
+ * Characteristics of barrel exports:
975
+ * - Named "index.ts" or "index.js"
976
+ * - Many re-export statements (export * from, export { x } from)
977
+ * - Little to no actual implementation code
978
+ * - High export count relative to lines of code
979
+ */
980
+ function isBarrelExport(node: DependencyNode): boolean {
981
+ const { file, exports, imports, linesOfCode } = node;
982
+
983
+ // Check filename pattern
984
+ const fileName = file.split('/').pop()?.toLowerCase();
985
+ const isIndexFile = fileName === 'index.ts' || fileName === 'index.js' ||
986
+ fileName === 'index.tsx' || fileName === 'index.jsx';
987
+
988
+ // Calculate re-export ratio
989
+ // Re-exports typically have form: export { x } from 'module' or export * from 'module'
990
+ // They have imports AND exports, with exports coming from those imports
991
+ const hasReExports = exports.length > 0 && imports.length > 0;
992
+ const highExportToLinesRatio = exports.length > 3 && linesOfCode < exports.length * 5;
993
+
994
+ // Little actual code (mostly import/export statements)
995
+ const sparseCode = linesOfCode > 0 && linesOfCode < 50 && exports.length >= 2;
996
+
997
+ // Index files with re-export patterns
998
+ if (isIndexFile && hasReExports) {
999
+ return true;
1000
+ }
1001
+
1002
+ // Non-index files that are clearly barrel exports
1003
+ if (highExportToLinesRatio && imports.length >= exports.length * 0.5) {
1004
+ return true;
1005
+ }
1006
+
1007
+ // Very sparse files with multiple re-exports
1008
+ if (sparseCode && imports.length > 0) {
1009
+ return true;
1010
+ }
1011
+
1012
+ return false;
1013
+ }
1014
+
1015
+ /**
1016
+ * Detect if a file is primarily a type definition file
1017
+ *
1018
+ * Characteristics:
1019
+ * - Mostly type/interface exports
1020
+ * - Little to no runtime code
1021
+ * - Often named *.d.ts or types.ts
1022
+ */
1023
+ function isTypeDefinitionFile(node: DependencyNode): boolean {
1024
+ const { file, exports } = node;
1025
+
1026
+ // Check filename pattern
1027
+ const fileName = file.split('/').pop()?.toLowerCase();
1028
+ const isTypesFile = fileName?.includes('types') || fileName?.includes('.d.ts') ||
1029
+ fileName === 'types.ts' || fileName === 'interfaces.ts';
1030
+
1031
+ // Count type exports vs other exports
1032
+ const typeExports = exports.filter(e => e.type === 'type' || e.type === 'interface');
1033
+ const runtimeExports = exports.filter(e => e.type === 'function' || e.type === 'class' || e.type === 'const');
1034
+
1035
+ // High ratio of type exports
1036
+ const mostlyTypes = exports.length > 0 &&
1037
+ typeExports.length > runtimeExports.length &&
1038
+ typeExports.length / exports.length > 0.7;
1039
+
1040
+ return isTypesFile || mostlyTypes;
1041
+ }
1042
+
1043
+ /**
1044
+ * Detect if a file is a config/schema file
1045
+ *
1046
+ * Characteristics:
1047
+ * - Named with config, schema, or settings patterns
1048
+ * - Often defines database schemas, configuration objects
1049
+ * - Multiple domains are acceptable (centralized config)
1050
+ */
1051
+ function isConfigOrSchemaFile(node: DependencyNode): boolean {
1052
+ const { file, exports } = node;
1053
+
1054
+ const fileName = file.split('/').pop()?.toLowerCase();
1055
+
1056
+ // Check filename patterns for config/schema files
1057
+ const configPatterns = [
1058
+ 'config', 'schema', 'settings', 'options', 'constants',
1059
+ 'env', 'environment', '.config.', '-config.', '_config.',
1060
+ ];
1061
+
1062
+ const isConfigName = configPatterns.some(pattern =>
1063
+ fileName?.includes(pattern) || fileName?.startsWith(pattern) || fileName?.endsWith(`${pattern}.ts`)
1064
+ );
1065
+
1066
+ // Check if file is in a config/settings directory
1067
+ const isConfigPath = file.toLowerCase().includes('/config/') ||
1068
+ file.toLowerCase().includes('/schemas/') ||
1069
+ file.toLowerCase().includes('/settings/');
1070
+
1071
+ // Check for schema-like exports (often have table/model definitions)
1072
+ const hasSchemaExports = exports.some(e =>
1073
+ e.name.toLowerCase().includes('table') ||
1074
+ e.name.toLowerCase().includes('schema') ||
1075
+ e.name.toLowerCase().includes('config') ||
1076
+ e.name.toLowerCase().includes('setting')
1077
+ );
1078
+
1079
+ return isConfigName || isConfigPath || hasSchemaExports;
1080
+ }
1081
+
1082
+ /**
1083
+ * Detect if a file is a utility/helper file
1084
+ *
1085
+ * Characteristics:
1086
+ * - Named with util, helper, or utility patterns
1087
+ * - Often contains mixed helper functions by design
1088
+ * - Multiple domains are acceptable (utility purpose)
1089
+ */
1090
+ function isUtilityFile(node: DependencyNode): boolean {
1091
+ const { file, exports } = node;
1092
+
1093
+ const fileName = file.split('/').pop()?.toLowerCase();
1094
+
1095
+ // Check filename patterns for utility files
1096
+ const utilityPatterns = [
1097
+ 'util', 'utility', 'utilities', 'helper', 'helpers',
1098
+ 'common', 'shared', 'lib', 'toolbox', 'toolkit',
1099
+ '.util.', '-util.', '_util.',
1100
+ ];
1101
+
1102
+ const isUtilityName = utilityPatterns.some(pattern =>
1103
+ fileName?.includes(pattern)
1104
+ );
1105
+
1106
+ // Check if file is in a utils/helpers directory
1107
+ const isUtilityPath = file.toLowerCase().includes('/utils/') ||
1108
+ file.toLowerCase().includes('/helpers/') ||
1109
+ file.toLowerCase().includes('/lib/') ||
1110
+ file.toLowerCase().includes('/common/');
1111
+
1112
+ // Check if file has many small utility-like exports
1113
+ const hasManySmallExports = exports.length >= 3 && exports.every(e =>
1114
+ e.type === 'function' || e.type === 'const'
1115
+ );
1116
+
1117
+ return isUtilityName || isUtilityPath || hasManySmallExports;
1118
+ }
1119
+
1120
+ /**
1121
+ * Adjust fragmentation score based on file classification
1122
+ *
1123
+ * This reduces false positives by:
1124
+ * - Ignoring fragmentation for barrel exports (they're meant to aggregate)
1125
+ * - Ignoring fragmentation for type definitions (centralized types are good)
1126
+ * - Reducing fragmentation for cohesive modules (large but focused is OK)
1127
+ */
1128
+ export function adjustFragmentationForClassification(
1129
+ baseFragmentation: number,
1130
+ classification: FileClassification
1131
+ ): number {
1132
+ switch (classification) {
1133
+ case 'barrel-export':
1134
+ // Barrel exports are meant to have multiple domains - no fragmentation
1135
+ return 0;
1136
+ case 'type-definition':
1137
+ // Centralized type definitions are good practice - no fragmentation
1138
+ return 0;
1139
+ case 'cohesive-module':
1140
+ // Cohesive modules get a significant discount
1141
+ return baseFragmentation * 0.3;
1142
+ case 'mixed-concerns':
1143
+ // Mixed concerns keep full fragmentation score
1144
+ return baseFragmentation;
1145
+ default:
1146
+ // Unknown gets a small discount (benefit of doubt)
1147
+ return baseFragmentation * 0.7;
1148
+ }
1149
+ }
1150
+
1151
+ /**
1152
+ * Get classification-specific recommendations
1153
+ */
1154
+ export function getClassificationRecommendations(
1155
+ classification: FileClassification,
1156
+ file: string,
1157
+ issues: string[]
1158
+ ): string[] {
1159
+ switch (classification) {
1160
+ case 'barrel-export':
1161
+ return [
1162
+ 'Barrel export file detected - multiple domains are expected here',
1163
+ 'Consider if this barrel export improves or hinders discoverability',
1164
+ ];
1165
+ case 'type-definition':
1166
+ return [
1167
+ 'Type definition file - centralized types improve consistency',
1168
+ 'Consider splitting if file becomes too large (>500 lines)',
1169
+ ];
1170
+ case 'cohesive-module':
1171
+ return [
1172
+ 'Module has good cohesion despite its size',
1173
+ 'Consider documenting the module boundaries for AI assistants',
1174
+ ];
1175
+ case 'mixed-concerns':
1176
+ return [
1177
+ 'Consider splitting this file by domain',
1178
+ 'Identify independent responsibilities and extract them',
1179
+ 'Review import dependencies to understand coupling',
1180
+ ];
1181
+ default:
1182
+ return issues;
1183
+ }
1184
+ }
package/src/index.ts CHANGED
@@ -11,6 +11,9 @@ import {
11
11
  detectModuleClusters,
12
12
  calculatePathEntropy,
13
13
  calculateDirectoryDistance,
14
+ classifyFile,
15
+ adjustFragmentationForClassification,
16
+ getClassificationRecommendations,
14
17
  } from './analyzer';
15
18
  import { calculateContextScore } from './scoring';
16
19
  import type {
@@ -22,6 +25,7 @@ import type {
22
25
  DomainSignals,
23
26
  CoUsageData,
24
27
  TypeDependency,
28
+ FileClassification,
25
29
  } from './types';
26
30
  import {
27
31
  buildCoUsageMatrix,
@@ -42,6 +46,12 @@ export type {
42
46
  DomainSignals,
43
47
  CoUsageData,
44
48
  TypeDependency,
49
+ FileClassification,
50
+ };
51
+
52
+ export {
53
+ classifyFile,
54
+ adjustFragmentationForClassification,
45
55
  };
46
56
 
47
57
  export {
@@ -196,6 +206,7 @@ export async function analyzeContext(
196
206
  contextBudget: metric.contextBudget,
197
207
  fragmentationScore: 0,
198
208
  relatedFiles: [],
209
+ fileClassification: 'unknown' as const, // Python files not yet classified
199
210
  severity,
200
211
  issues,
201
212
  recommendations,
@@ -275,6 +286,41 @@ export async function analyzeContext(
275
286
  ...new Set(node.exports.map((e) => e.inferredDomain || 'unknown')),
276
287
  ];
277
288
 
289
+ // Classify the file to help distinguish real issues from false positives
290
+ const fileClassification = classifyFile(node, cohesionScore, domains);
291
+
292
+ // Adjust fragmentation based on classification
293
+ const adjustedFragmentationScore = adjustFragmentationForClassification(
294
+ fragmentationScore,
295
+ fileClassification
296
+ );
297
+
298
+ // Get classification-specific recommendations
299
+ const classificationRecommendations = getClassificationRecommendations(
300
+ fileClassification,
301
+ file,
302
+ issues
303
+ );
304
+
305
+ // Re-analyze issues with adjusted fragmentation
306
+ const {
307
+ severity: adjustedSeverity,
308
+ issues: adjustedIssues,
309
+ recommendations: finalRecommendations,
310
+ potentialSavings: adjustedSavings,
311
+ } = analyzeIssues({
312
+ file,
313
+ importDepth,
314
+ contextBudget,
315
+ cohesionScore,
316
+ fragmentationScore: adjustedFragmentationScore,
317
+ maxDepth,
318
+ maxContextBudget,
319
+ minCohesion,
320
+ maxFragmentation,
321
+ circularDeps,
322
+ });
323
+
278
324
  results.push({
279
325
  file,
280
326
  tokenCost: node.tokenCost,
@@ -287,12 +333,13 @@ export async function analyzeContext(
287
333
  domains,
288
334
  exportCount: node.exports.length,
289
335
  contextBudget,
290
- fragmentationScore,
336
+ fragmentationScore: adjustedFragmentationScore,
291
337
  relatedFiles,
292
- severity,
293
- issues,
294
- recommendations,
295
- potentialSavings,
338
+ fileClassification,
339
+ severity: adjustedSeverity,
340
+ issues: adjustedIssues,
341
+ recommendations: [...finalRecommendations, ...classificationRecommendations.slice(0, 1)],
342
+ potentialSavings: adjustedSavings,
296
343
  });
297
344
  }
298
345
 
package/src/types.ts CHANGED
@@ -32,6 +32,9 @@ export interface ContextAnalysisResult {
32
32
  fragmentationScore: number; // 0-1, how scattered is this domain (0 = well-grouped)
33
33
  relatedFiles: string[]; // Files that should be loaded together
34
34
 
35
+ // File classification (NEW)
36
+ fileClassification: FileClassification; // Type of file for analysis context
37
+
35
38
  // Recommendations
36
39
  severity: 'critical' | 'major' | 'minor' | 'info';
37
40
  issues: string[]; // List of specific problems
@@ -39,6 +42,17 @@ export interface ContextAnalysisResult {
39
42
  potentialSavings: number; // Estimated token savings if fixed
40
43
  }
41
44
 
45
+ /**
46
+ * Classification of file type for analysis context
47
+ * Helps distinguish real issues from false positives
48
+ */
49
+ export type FileClassification =
50
+ | 'barrel-export' // Re-exports from other modules (index.ts files)
51
+ | 'type-definition' // Primarily type/interface definitions
52
+ | 'cohesive-module' // Single domain, high cohesion (acceptable large files)
53
+ | 'mixed-concerns' // Multiple domains, potential refactoring candidate
54
+ | 'unknown'; // Unable to classify
55
+
42
56
  export interface ModuleCluster {
43
57
  domain: string; // e.g., "user-management", "auth"
44
58
  files: string[];