@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/.turbo/turbo-build.log +11 -11
- package/.turbo/turbo-lint.log +5 -6
- package/.turbo/turbo-test.log +26 -26
- package/dist/chunk-2HE27YEV.mjs +1739 -0
- package/dist/chunk-D25B5LZR.mjs +1739 -0
- package/dist/chunk-KDUUZQBK.mjs +1692 -0
- package/dist/chunk-KWIS5FQP.mjs +1739 -0
- package/dist/chunk-RRB2C34Q.mjs +1738 -0
- package/dist/chunk-XTAXUNQN.mjs +1742 -0
- package/dist/cli.js +136 -144
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +72 -78
- package/dist/index.d.ts +72 -78
- package/dist/index.js +137 -169
- package/dist/index.mjs +4 -27
- package/package.json +2 -2
- package/src/analyzer.ts +17 -9
- package/src/ast-utils.ts +2 -2
- package/src/classifier.ts +13 -328
- package/src/cli-action.ts +7 -0
- package/src/cli.ts +0 -17
- package/src/cluster-detector.ts +27 -0
- package/src/defaults.ts +3 -0
- package/src/graph-builder.ts +1 -6
- package/src/heuristics.ts +216 -0
- package/src/issue-analyzer.ts +15 -0
- package/src/metrics.ts +9 -0
- package/src/scoring.ts +3 -5
- package/src/semantic-analysis.ts +7 -0
- package/src/summary.ts +1 -1
- package/src/types.ts +52 -20
- package/src/utils/string-utils.ts +6 -2
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();
|
package/src/cluster-detector.ts
CHANGED
|
@@ -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,
|
package/src/graph-builder.ts
CHANGED
|
@@ -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
|
+
}
|