@aiready/context-analyzer 0.21.6 → 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/src/classifier.ts CHANGED
@@ -1,4 +1,16 @@
1
1
  import type { DependencyNode, FileClassification } from './types';
2
+ import {
3
+ isBarrelExport,
4
+ isTypeDefinition,
5
+ isNextJsPage,
6
+ isLambdaHandler,
7
+ isServiceFile,
8
+ isEmailTemplate,
9
+ isParserFile,
10
+ isSessionFile,
11
+ isUtilityModule,
12
+ isConfigFile,
13
+ } from './heuristics';
2
14
 
3
15
  /**
4
16
  * Constants for file classifications to avoid magic strings
@@ -99,334 +111,7 @@ export function classifyFile(
99
111
  return Classification.UNKNOWN;
100
112
  }
101
113
 
102
- /**
103
- * Detect if a file is a barrel export (index.ts)
104
- *
105
- * @param node The dependency node to check
106
- * @returns True if the file appears to be a barrel export
107
- */
108
- export function isBarrelExport(node: DependencyNode): boolean {
109
- const { file, exports } = node;
110
- const fileName = file.split('/').pop()?.toLowerCase();
111
-
112
- // Barrel files are typically named index.ts or index.js
113
- const isIndexFile = fileName === 'index.ts' || fileName === 'index.js';
114
-
115
- // Small file with many exports is likely a barrel
116
- const isSmallAndManyExports =
117
- node.tokenCost < 1000 && (exports || []).length > 5;
118
-
119
- // RE-EXPORT HEURISTIC for non-index files
120
- const isReexportPattern =
121
- (exports || []).length >= 5 &&
122
- (exports || []).every(
123
- (e) =>
124
- e.type === 'const' ||
125
- e.type === 'function' ||
126
- e.type === 'type' ||
127
- e.type === 'interface'
128
- );
129
-
130
- return !!isIndexFile || !!isSmallAndManyExports || !!isReexportPattern;
131
- }
132
-
133
- /**
134
- * Detect if a file is primarily type definitions
135
- *
136
- * @param node The dependency node to check
137
- * @returns True if the file appears to be primarily types
138
- */
139
- export function isTypeDefinition(node: DependencyNode): boolean {
140
- const { file } = node;
141
-
142
- // Check file extension
143
- if (file.endsWith('.d.ts')) return true;
144
-
145
- // Check if all exports are types or interfaces
146
- const nodeExports = node.exports || [];
147
- const hasExports = nodeExports.length > 0;
148
- const areAllTypes =
149
- hasExports &&
150
- nodeExports.every((e) => e.type === 'type' || e.type === 'interface');
151
- const allTypes: boolean = !!areAllTypes;
152
-
153
- // Check if path includes 'types' or 'interfaces'
154
- const isTypePath =
155
- file.toLowerCase().includes('/types/') ||
156
- file.toLowerCase().includes('/interfaces/') ||
157
- file.toLowerCase().includes('/models/');
158
-
159
- return allTypes || (isTypePath && hasExports);
160
- }
161
-
162
- /**
163
- * Detect if a file is a utility module
164
- *
165
- * @param node The dependency node to check
166
- * @returns True if the file appears to be a utility module
167
- */
168
- export function isUtilityModule(node: DependencyNode): boolean {
169
- const { file } = node;
170
-
171
- // Check if path includes 'utils', 'helpers', etc.
172
- const isUtilPath =
173
- file.toLowerCase().includes('/utils/') ||
174
- file.toLowerCase().includes('/helpers/') ||
175
- file.toLowerCase().includes('/util/') ||
176
- file.toLowerCase().includes('/helper/');
177
-
178
- const fileName = file.split('/').pop()?.toLowerCase();
179
- const isUtilName =
180
- fileName?.includes('utils.') ||
181
- fileName?.includes('helpers.') ||
182
- fileName?.includes('util.') ||
183
- fileName?.includes('helper.');
184
-
185
- return !!isUtilPath || !!isUtilName;
186
- }
187
-
188
- /**
189
- * Detect if a file is a Lambda/API handler
190
- *
191
- * @param node The dependency node to check
192
- * @returns True if the file appears to be a Lambda handler
193
- */
194
- export function isLambdaHandler(node: DependencyNode): boolean {
195
- const { file, exports } = node;
196
- const fileName = file.split('/').pop()?.toLowerCase();
197
-
198
- const handlerPatterns = [
199
- 'handler',
200
- '.handler.',
201
- '-handler.',
202
- 'lambda',
203
- '.lambda.',
204
- '-lambda.',
205
- ];
206
- const isHandlerName = handlerPatterns.some((pattern) =>
207
- fileName?.includes(pattern)
208
- );
209
-
210
- const isHandlerPath =
211
- file.toLowerCase().includes('/handlers/') ||
212
- file.toLowerCase().includes('/lambdas/') ||
213
- file.toLowerCase().includes('/lambda/') ||
214
- file.toLowerCase().includes('/functions/');
215
-
216
- const hasHandlerExport = (exports || []).some(
217
- (e) =>
218
- e.name.toLowerCase() === 'handler' ||
219
- e.name.toLowerCase() === 'main' ||
220
- e.name.toLowerCase() === 'lambdahandler' ||
221
- e.name.toLowerCase().endsWith('handler')
222
- );
223
-
224
- return !!isHandlerName || !!isHandlerPath || !!hasHandlerExport;
225
- }
226
-
227
- /**
228
- * Detect if a file is a service file
229
- *
230
- * @param node The dependency node to check
231
- * @returns True if the file appears to be a service file
232
- */
233
- export function isServiceFile(node: DependencyNode): boolean {
234
- const { file, exports } = node;
235
- const fileName = file.split('/').pop()?.toLowerCase();
236
-
237
- const servicePatterns = ['service', '.service.', '-service.', '_service.'];
238
- const isServiceName = servicePatterns.some((pattern) =>
239
- fileName?.includes(pattern)
240
- );
241
- const isServicePath = file.toLowerCase().includes('/services/');
242
- const hasServiceNamedExport = (exports || []).some(
243
- (e) =>
244
- e.name.toLowerCase().includes('service') ||
245
- e.name.toLowerCase().endsWith('service')
246
- );
247
- const hasClassExport = (exports || []).some((e) => e.type === 'class');
248
-
249
- return (
250
- !!isServiceName ||
251
- !!isServicePath ||
252
- (!!hasServiceNamedExport && !!hasClassExport)
253
- );
254
- }
255
-
256
- /**
257
- * Detect if a file is an email template/layout
258
- *
259
- * @param node The dependency node to check
260
- * @returns True if the file appears to be an email template
261
- */
262
- export function isEmailTemplate(node: DependencyNode): boolean {
263
- const { file, exports } = node;
264
- const fileName = file.split('/').pop()?.toLowerCase();
265
-
266
- const emailTemplatePatterns = [
267
- '-email-',
268
- '.email.',
269
- '_email_',
270
- '-template',
271
- '.template.',
272
- '_template',
273
- '-mail.',
274
- '.mail.',
275
- ];
276
- const isEmailTemplateName = emailTemplatePatterns.some((pattern) =>
277
- fileName?.includes(pattern)
278
- );
279
- const isEmailPath =
280
- file.toLowerCase().includes('/emails/') ||
281
- file.toLowerCase().includes('/mail/') ||
282
- file.toLowerCase().includes('/notifications/');
283
-
284
- const hasTemplateFunction = (exports || []).some(
285
- (e) =>
286
- e.type === 'function' &&
287
- (e.name.toLowerCase().startsWith('render') ||
288
- e.name.toLowerCase().startsWith('generate') ||
289
- (e.name.toLowerCase().includes('template') &&
290
- e.name.toLowerCase().includes('email')))
291
- );
292
-
293
- return !!isEmailPath || !!isEmailTemplateName || !!hasTemplateFunction;
294
- }
295
-
296
- /**
297
- * Detect if a file is a parser/transformer
298
- *
299
- * @param node The dependency node to check
300
- * @returns True if the file appears to be a parser
301
- */
302
- export function isParserFile(node: DependencyNode): boolean {
303
- const { file, exports } = node;
304
- const fileName = file.split('/').pop()?.toLowerCase();
305
-
306
- const parserPatterns = [
307
- 'parser',
308
- '.parser.',
309
- '-parser.',
310
- '_parser.',
311
- 'transform',
312
- '.transform.',
313
- 'converter',
314
- 'mapper',
315
- 'serializer',
316
- ];
317
- const isParserName = parserPatterns.some((pattern) =>
318
- fileName?.includes(pattern)
319
- );
320
- const isParserPath =
321
- file.toLowerCase().includes('/parsers/') ||
322
- file.toLowerCase().includes('/transformers/');
323
-
324
- const hasParseFunction = (exports || []).some(
325
- (e) =>
326
- e.type === 'function' &&
327
- (e.name.toLowerCase().startsWith('parse') ||
328
- e.name.toLowerCase().startsWith('transform') ||
329
- e.name.toLowerCase().startsWith('extract'))
330
- );
331
-
332
- return !!isParserName || !!isParserPath || !!hasParseFunction;
333
- }
334
-
335
- /**
336
- * Detect if a file is a session/state management file
337
- *
338
- * @param node The dependency node to check
339
- * @returns True if the file appears to be a session/state file
340
- */
341
- export function isSessionFile(node: DependencyNode): boolean {
342
- const { file, exports } = node;
343
- const fileName = file.split('/').pop()?.toLowerCase();
344
-
345
- const sessionPatterns = ['session', 'state', 'context', 'store'];
346
- const isSessionName = sessionPatterns.some((pattern) =>
347
- fileName?.includes(pattern)
348
- );
349
- const isSessionPath =
350
- file.toLowerCase().includes('/sessions/') ||
351
- file.toLowerCase().includes('/state/');
352
-
353
- const hasSessionExport = (exports || []).some(
354
- (e) =>
355
- e.name.toLowerCase().includes('session') ||
356
- e.name.toLowerCase().includes('state') ||
357
- e.name.toLowerCase().includes('store')
358
- );
359
-
360
- return !!isSessionName || !!isSessionPath || !!hasSessionExport;
361
- }
362
-
363
- /**
364
- * Detect if a file is a configuration or schema file
365
- *
366
- * @param node The dependency node to check
367
- * @returns True if the file appears to be a config file
368
- */
369
- export function isConfigFile(node: DependencyNode): boolean {
370
- const { file, exports } = node;
371
- const lowerPath = file.toLowerCase();
372
- const fileName = file.split('/').pop()?.toLowerCase();
373
-
374
- const configPatterns = [
375
- '.config.',
376
- 'tsconfig',
377
- 'jest.config',
378
- 'package.json',
379
- 'aiready.json',
380
- 'next.config',
381
- 'sst.config',
382
- ];
383
- const isConfigName = configPatterns.some((p) => fileName?.includes(p));
384
- const isConfigPath =
385
- lowerPath.includes('/config/') ||
386
- lowerPath.includes('/settings/') ||
387
- lowerPath.includes('/schemas/');
388
-
389
- const hasSchemaExports = (exports || []).some(
390
- (e) =>
391
- e.name.toLowerCase().includes('schema') ||
392
- e.name.toLowerCase().includes('config') ||
393
- e.name.toLowerCase().includes('setting')
394
- );
395
-
396
- return !!isConfigName || !!isConfigPath || !!hasSchemaExports;
397
- }
398
-
399
- /**
400
- * Detect if a file is a Next.js App Router page
401
- *
402
- * @param node The dependency node to check
403
- * @returns True if the file appears to be a Next.js page
404
- */
405
- export function isNextJsPage(node: DependencyNode): boolean {
406
- const { file, exports } = node;
407
- const lowerPath = file.toLowerCase();
408
- const fileName = file.split('/').pop()?.toLowerCase();
409
-
410
- const isInAppDir =
411
- lowerPath.includes('/app/') || lowerPath.startsWith('app/');
412
- const isPageFile = fileName === 'page.tsx' || fileName === 'page.ts';
413
-
414
- if (!isInAppDir || !isPageFile) return false;
415
-
416
- const hasDefaultExport = (exports || []).some((e) => e.type === 'default');
417
- const nextJsExports = [
418
- 'metadata',
419
- 'generatemetadata',
420
- 'faqjsonld',
421
- 'jsonld',
422
- 'icon',
423
- ];
424
- const hasNextJsExports = (exports || []).some((e) =>
425
- nextJsExports.includes(e.name.toLowerCase())
426
- );
427
-
428
- return !!hasDefaultExport || !!hasNextJsExports;
429
- }
114
+ // [Split Point] Logic below this point handled by heuristics.ts
430
115
 
431
116
  /**
432
117
  * Adjust cohesion score based on file classification
package/src/cli-action.ts CHANGED
@@ -15,6 +15,13 @@ import {
15
15
  import chalk from 'chalk';
16
16
  import { writeFileSync } from 'fs';
17
17
 
18
+ /**
19
+ * Orchestrates the context analysis CLI command.
20
+ * Merges configuration, invokes the analyzer, and formats the output (Console/JSON/HTML).
21
+ *
22
+ * @param directory - Root directory to analyze
23
+ * @param options - CLI options including focus area, max depth, and output format
24
+ */
18
25
  export async function contextActionHandler(directory: string, options: any) {
19
26
  console.log(chalk.blue('🔍 Analyzing context window costs...\n'));
20
27
 
package/src/cli.ts CHANGED
@@ -1,23 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { Command } from 'commander';
4
- import { analyzeContext } from './analyzer';
5
- import { generateSummary } from './summary';
6
- import {
7
- displayConsoleReport,
8
- generateHTMLReport,
9
- runInteractiveSetup,
10
- } from './utils/output-formatter';
11
- import chalk from 'chalk';
12
- import { writeFileSync } from 'fs';
13
- import {
14
- loadMergedConfig,
15
- handleJSONOutput,
16
- handleCLIError,
17
- getElapsedTime,
18
- resolveOutputPath,
19
- } from '@aiready/core';
20
-
21
4
  import { contextActionHandler } from './cli-action';
22
5
 
23
6
  const program = new Command();
@@ -3,6 +3,10 @@ import { calculateFragmentation, calculateEnhancedCohesion } from './metrics';
3
3
 
4
4
  /**
5
5
  * Group files by domain to detect module clusters
6
+ * @param graph - The dependency graph to analyze
7
+ * @param options - Optional configuration options
8
+ * @param options.useLogScale - Whether to use logarithmic scaling for calculations
9
+ * @returns Array of module clusters
6
10
  */
7
11
  export function detectModuleClusters(
8
12
  graph: DependencyGraph,
@@ -20,6 +24,29 @@ export function detectModuleClusters(
20
24
 
21
25
  const clusters: ModuleCluster[] = [];
22
26
 
27
+ const generateSuggestedStructure = (
28
+ files: string[],
29
+ tokens: number,
30
+ fragmentation: number
31
+ ) => {
32
+ const targetFiles = Math.max(1, Math.ceil(tokens / 10000));
33
+ const plan: string[] = [];
34
+
35
+ if (fragmentation > 0.5) {
36
+ plan.push(
37
+ `Consolidate ${files.length} files scattered across multiple directories into ${targetFiles} core module(s)`
38
+ );
39
+ }
40
+
41
+ if (tokens > 20000) {
42
+ plan.push(
43
+ `Domain logic is very large (${Math.round(tokens / 1000)}k tokens). Ensure clear sub-domain boundaries.`
44
+ );
45
+ }
46
+
47
+ return { targetFiles, consolidationPlan: plan };
48
+ };
49
+
23
50
  for (const [domain, files] of domainMap.entries()) {
24
51
  if (files.length < 2 || domain === 'unknown') continue;
25
52
 
package/src/defaults.ts CHANGED
@@ -4,6 +4,9 @@ import type { ContextAnalyzerOptions } from './types';
4
4
  /**
5
5
  * Generate smart defaults for context analysis based on repository size
6
6
  * Automatically tunes thresholds to target ~10 most serious issues
7
+ * @param directory - The root directory to analyze
8
+ * @param userOptions - Partial user-provided options to merge with defaults
9
+ * @returns Complete ContextAnalyzerOptions with smart defaults
7
10
  */
8
11
  export async function getSmartDefaults(
9
12
  directory: string,
@@ -1,4 +1,4 @@
1
- import { estimateTokens, parseFileExports } from '@aiready/core';
1
+ import { estimateTokens, parseFileExports, FileContent } from '@aiready/core';
2
2
  import { singularize } from './utils/string-utils';
3
3
  import {
4
4
  calculateImportDepthFromEdges,
@@ -14,11 +14,6 @@ import {
14
14
  import { extractExportsWithAST } from './ast-utils';
15
15
  import { join, dirname, normalize } from 'path';
16
16
 
17
- interface FileContent {
18
- file: string;
19
- content: string;
20
- }
21
-
22
17
  /**
23
18
  * Resolve an import source to its absolute path considering the importing file's location
24
19
  */
@@ -0,0 +1,216 @@
1
+ import { DependencyNode } from './types';
2
+
3
+ /**
4
+ * Detect if a file is a barrel export (index.ts)
5
+ */
6
+ export function isBarrelExport(node: DependencyNode): boolean {
7
+ const { file, exports } = node;
8
+ const fileName = file.split('/').pop()?.toLowerCase();
9
+
10
+ const isIndexFile = fileName === 'index.ts' || fileName === 'index.js';
11
+ const isSmallAndManyExports =
12
+ node.tokenCost < 1000 && (exports || []).length > 5;
13
+
14
+ const isReexportPattern =
15
+ (exports || []).length >= 5 &&
16
+ (exports || []).every((e) =>
17
+ ['const', 'function', 'type', 'interface'].includes(e.type)
18
+ );
19
+
20
+ return !!isIndexFile || !!isSmallAndManyExports || !!isReexportPattern;
21
+ }
22
+
23
+ /**
24
+ * Detect if a file is primarily type definitions
25
+ */
26
+ export function isTypeDefinition(node: DependencyNode): boolean {
27
+ const { file } = node;
28
+ if (file.endsWith('.d.ts')) return true;
29
+
30
+ const nodeExports = node.exports || [];
31
+ const hasExports = nodeExports.length > 0;
32
+ const areAllTypes =
33
+ hasExports &&
34
+ nodeExports.every((e) => e.type === 'type' || e.type === 'interface');
35
+
36
+ const isTypePath = /\/(types|interfaces|models)\//i.test(file);
37
+ return !!areAllTypes || (isTypePath && hasExports);
38
+ }
39
+
40
+ /**
41
+ * Detect if a file is a utility module
42
+ */
43
+ export function isUtilityModule(node: DependencyNode): boolean {
44
+ const { file } = node;
45
+ const isUtilPath = /\/(utils|helpers|util|helper)\//i.test(file);
46
+ const fileName = file.split('/').pop()?.toLowerCase() || '';
47
+ const isUtilName = /(utils\.|helpers\.|util\.|helper\.)/i.test(fileName);
48
+ return isUtilPath || isUtilName;
49
+ }
50
+
51
+ /**
52
+ * Detect if a file is a Lambda/API handler
53
+ */
54
+ export function isLambdaHandler(node: DependencyNode): boolean {
55
+ const { file, exports } = node;
56
+ const fileName = file.split('/').pop()?.toLowerCase() || '';
57
+ const handlerPatterns = [
58
+ 'handler',
59
+ '.handler.',
60
+ '-handler.',
61
+ 'lambda',
62
+ '.lambda.',
63
+ '-lambda.',
64
+ ];
65
+ const isHandlerName = handlerPatterns.some((p) => fileName.includes(p));
66
+ const isHandlerPath = /\/(handlers|lambdas|lambda|functions)\//i.test(file);
67
+ const hasHandlerExport = (exports || []).some(
68
+ (e) =>
69
+ ['handler', 'main', 'lambdahandler'].includes(e.name.toLowerCase()) ||
70
+ e.name.toLowerCase().endsWith('handler')
71
+ );
72
+ return isHandlerName || isHandlerPath || hasHandlerExport;
73
+ }
74
+
75
+ /**
76
+ * Detect if a file is a service file
77
+ */
78
+ export function isServiceFile(node: DependencyNode): boolean {
79
+ const { file, exports } = node;
80
+ const fileName = file.split('/').pop()?.toLowerCase() || '';
81
+ const servicePatterns = ['service', '.service.', '-service.', '_service.'];
82
+ const isServiceName = servicePatterns.some((p) => fileName.includes(p));
83
+ const isServicePath = file.toLowerCase().includes('/services/');
84
+ const hasServiceNamedExport = (exports || []).some((e) =>
85
+ e.name.toLowerCase().includes('service')
86
+ );
87
+ const hasClassExport = (exports || []).some((e) => e.type === 'class');
88
+ return (
89
+ isServiceName || isServicePath || (hasServiceNamedExport && hasClassExport)
90
+ );
91
+ }
92
+
93
+ /**
94
+ * Detect if a file is an email template/layout
95
+ */
96
+ export function isEmailTemplate(node: DependencyNode): boolean {
97
+ const { file, exports } = node;
98
+ const fileName = file.split('/').pop()?.toLowerCase() || '';
99
+ const emailPatterns = [
100
+ '-email-',
101
+ '.email.',
102
+ '_email_',
103
+ '-template',
104
+ '.template.',
105
+ '_template',
106
+ '-mail.',
107
+ '.mail.',
108
+ ];
109
+ const isEmailName = emailPatterns.some((p) => fileName.includes(p));
110
+ const isEmailPath = /\/(emails|mail|notifications)\//i.test(file);
111
+ const hasTemplateFunction = (exports || []).some(
112
+ (e) =>
113
+ e.type === 'function' &&
114
+ (e.name.toLowerCase().startsWith('render') ||
115
+ e.name.toLowerCase().startsWith('generate'))
116
+ );
117
+ return isEmailPath || isEmailName || hasTemplateFunction;
118
+ }
119
+
120
+ /**
121
+ * Detect if a file is a parser/transformer
122
+ */
123
+ export function isParserFile(node: DependencyNode): boolean {
124
+ const { file, exports } = node;
125
+ const fileName = file.split('/').pop()?.toLowerCase() || '';
126
+ const parserPatterns = [
127
+ 'parser',
128
+ '.parser.',
129
+ '-parser.',
130
+ '_parser.',
131
+ 'transform',
132
+ 'converter',
133
+ 'mapper',
134
+ 'serializer',
135
+ ];
136
+ const isParserName = parserPatterns.some((p) => fileName.includes(p));
137
+ const isParserPath = /\/(parsers|transformers)\//i.test(file);
138
+ const hasParseFunction = (exports || []).some(
139
+ (e) =>
140
+ e.type === 'function' &&
141
+ (e.name.toLowerCase().startsWith('parse') ||
142
+ e.name.toLowerCase().startsWith('transform'))
143
+ );
144
+ return isParserName || isParserPath || hasParseFunction;
145
+ }
146
+
147
+ /**
148
+ * Detect if a file is a session/state management file
149
+ */
150
+ export function isSessionFile(node: DependencyNode): boolean {
151
+ const { file, exports } = node;
152
+ const fileName = file.split('/').pop()?.toLowerCase() || '';
153
+ const sessionPatterns = ['session', 'state', 'context', 'store'];
154
+ const isSessionName = sessionPatterns.some((p) => fileName.includes(p));
155
+ const isSessionPath = /\/(sessions|state)\//i.test(file);
156
+ const hasSessionExport = (exports || []).some((e) =>
157
+ ['session', 'state', 'store'].some((p) => e.name.toLowerCase().includes(p))
158
+ );
159
+ return isSessionName || isSessionPath || hasSessionExport;
160
+ }
161
+
162
+ /**
163
+ * Detect if a file is a Next.js App Router page
164
+ */
165
+ export function isNextJsPage(node: DependencyNode): boolean {
166
+ const { file, exports } = node;
167
+ const lowerPath = file.toLowerCase();
168
+ const fileName = file.split('/').pop()?.toLowerCase() || '';
169
+
170
+ const isInAppDir =
171
+ lowerPath.includes('/app/') || lowerPath.startsWith('app/');
172
+ if (!isInAppDir || (fileName !== 'page.tsx' && fileName !== 'page.ts'))
173
+ return false;
174
+
175
+ const hasDefaultExport = (exports || []).some((e) => e.type === 'default');
176
+ const nextJsExports = [
177
+ 'metadata',
178
+ 'generatemetadata',
179
+ 'faqjsonld',
180
+ 'jsonld',
181
+ 'icon',
182
+ ];
183
+ const hasNextJsExport = (exports || []).some((e) =>
184
+ nextJsExports.includes(e.name.toLowerCase())
185
+ );
186
+
187
+ return hasDefaultExport || hasNextJsExport;
188
+ }
189
+
190
+ /**
191
+ * Detect if a file is a configuration or schema file
192
+ */
193
+ export function isConfigFile(node: DependencyNode): boolean {
194
+ const { file, exports } = node;
195
+ const lowerPath = file.toLowerCase();
196
+ const fileName = file.split('/').pop()?.toLowerCase() || '';
197
+
198
+ const configPatterns = [
199
+ '.config.',
200
+ 'tsconfig',
201
+ 'jest.config',
202
+ 'package.json',
203
+ 'aiready.json',
204
+ 'next.config',
205
+ 'sst.config',
206
+ ];
207
+ const isConfigName = configPatterns.some((p) => fileName.includes(p));
208
+ const isConfigPath = /\/(config|settings|schemas)\//i.test(lowerPath);
209
+ const hasSchemaExport = (exports || []).some((e) =>
210
+ ['schema', 'config', 'setting'].some((p) =>
211
+ e.name.toLowerCase().includes(p)
212
+ )
213
+ );
214
+
215
+ return isConfigName || isConfigPath || hasSchemaExport;
216
+ }