@aiready/consistency 0.5.0 → 0.6.1

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/dist/index.mjs CHANGED
@@ -1,12 +1,589 @@
1
1
  import {
2
2
  analyzeConsistency,
3
- analyzeNaming,
4
- analyzePatterns,
5
- detectNamingConventions
6
- } from "./chunk-TXHPUU7A.mjs";
3
+ analyzeNamingAST,
4
+ analyzePatterns
5
+ } from "./chunk-LD3CHHU2.mjs";
6
+
7
+ // src/analyzers/naming.ts
8
+ import { readFileContent, loadConfig } from "@aiready/core";
9
+ import { dirname } from "path";
10
+ var COMMON_SHORT_WORDS = /* @__PURE__ */ new Set([
11
+ // Full English words (1-3 letters)
12
+ "day",
13
+ "key",
14
+ "net",
15
+ "to",
16
+ "go",
17
+ "for",
18
+ "not",
19
+ "new",
20
+ "old",
21
+ "top",
22
+ "end",
23
+ "run",
24
+ "try",
25
+ "use",
26
+ "get",
27
+ "set",
28
+ "add",
29
+ "put",
30
+ "map",
31
+ "log",
32
+ "row",
33
+ "col",
34
+ "tab",
35
+ "box",
36
+ "div",
37
+ "nav",
38
+ "tag",
39
+ "any",
40
+ "all",
41
+ "one",
42
+ "two",
43
+ "out",
44
+ "off",
45
+ "on",
46
+ "yes",
47
+ "no",
48
+ "now",
49
+ "max",
50
+ "min",
51
+ "sum",
52
+ "avg",
53
+ "ref",
54
+ "src",
55
+ "dst",
56
+ "raw",
57
+ "def",
58
+ "sub",
59
+ "pub",
60
+ "pre",
61
+ "mid",
62
+ "alt",
63
+ "opt",
64
+ "tmp",
65
+ "ext",
66
+ "sep",
67
+ // Prepositions and conjunctions
68
+ "and",
69
+ "from",
70
+ "how",
71
+ "pad",
72
+ "bar",
73
+ "non",
74
+ // Additional full words commonly flagged
75
+ "tax",
76
+ "cat",
77
+ "dog",
78
+ "car",
79
+ "bus",
80
+ "web",
81
+ "app",
82
+ "war",
83
+ "law",
84
+ "pay",
85
+ "buy",
86
+ "win",
87
+ "cut",
88
+ "hit",
89
+ "hot",
90
+ "pop",
91
+ "job",
92
+ "age",
93
+ "act",
94
+ "let",
95
+ "lot",
96
+ "bad",
97
+ "big",
98
+ "far",
99
+ "few",
100
+ "own",
101
+ "per",
102
+ "red",
103
+ "low",
104
+ "see",
105
+ "six",
106
+ "ten",
107
+ "way",
108
+ "who",
109
+ "why",
110
+ "yet",
111
+ "via",
112
+ "due",
113
+ "fee",
114
+ "fun",
115
+ "gas",
116
+ "gay",
117
+ "god",
118
+ "gun",
119
+ "guy",
120
+ "ice",
121
+ "ill",
122
+ "kid",
123
+ "mad",
124
+ "man",
125
+ "mix",
126
+ "mom",
127
+ "mrs",
128
+ "nor",
129
+ "odd",
130
+ "oil",
131
+ "pan",
132
+ "pet",
133
+ "pit",
134
+ "pot",
135
+ "pow",
136
+ "pro",
137
+ "raw",
138
+ "rep",
139
+ "rid",
140
+ "sad",
141
+ "sea",
142
+ "sit",
143
+ "sky",
144
+ "son",
145
+ "tea",
146
+ "tie",
147
+ "tip",
148
+ "van",
149
+ "war",
150
+ "win",
151
+ "won"
152
+ ]);
153
+ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
154
+ // Standard identifiers
155
+ "id",
156
+ "uid",
157
+ "gid",
158
+ "pid",
159
+ // Loop counters and iterators
160
+ "i",
161
+ "j",
162
+ "k",
163
+ "n",
164
+ "m",
165
+ // Web/Network
166
+ "url",
167
+ "uri",
168
+ "api",
169
+ "cdn",
170
+ "dns",
171
+ "ip",
172
+ "tcp",
173
+ "udp",
174
+ "http",
175
+ "ssl",
176
+ "tls",
177
+ "utm",
178
+ "seo",
179
+ "rss",
180
+ "xhr",
181
+ "ajax",
182
+ "cors",
183
+ "ws",
184
+ "wss",
185
+ // Data formats
186
+ "json",
187
+ "xml",
188
+ "yaml",
189
+ "csv",
190
+ "html",
191
+ "css",
192
+ "svg",
193
+ "pdf",
194
+ // File types & extensions
195
+ "img",
196
+ "txt",
197
+ "doc",
198
+ "docx",
199
+ "xlsx",
200
+ "ppt",
201
+ "md",
202
+ "rst",
203
+ "jpg",
204
+ "png",
205
+ "gif",
206
+ // Databases
207
+ "db",
208
+ "sql",
209
+ "orm",
210
+ "dao",
211
+ "dto",
212
+ "ddb",
213
+ "rds",
214
+ "nosql",
215
+ // File system
216
+ "fs",
217
+ "dir",
218
+ "tmp",
219
+ "src",
220
+ "dst",
221
+ "bin",
222
+ "lib",
223
+ "pkg",
224
+ // Operating system
225
+ "os",
226
+ "env",
227
+ "arg",
228
+ "cli",
229
+ "cmd",
230
+ "exe",
231
+ "cwd",
232
+ "pwd",
233
+ // UI/UX
234
+ "ui",
235
+ "ux",
236
+ "gui",
237
+ "dom",
238
+ "ref",
239
+ // Request/Response
240
+ "req",
241
+ "res",
242
+ "ctx",
243
+ "err",
244
+ "msg",
245
+ "auth",
246
+ // Mathematics/Computing
247
+ "max",
248
+ "min",
249
+ "avg",
250
+ "sum",
251
+ "abs",
252
+ "cos",
253
+ "sin",
254
+ "tan",
255
+ "log",
256
+ "exp",
257
+ "pow",
258
+ "sqrt",
259
+ "std",
260
+ "var",
261
+ "int",
262
+ "num",
263
+ "idx",
264
+ // Time
265
+ "now",
266
+ "utc",
267
+ "tz",
268
+ "ms",
269
+ "sec",
270
+ "hr",
271
+ "min",
272
+ "yr",
273
+ "mo",
274
+ // Common patterns
275
+ "app",
276
+ "cfg",
277
+ "config",
278
+ "init",
279
+ "len",
280
+ "val",
281
+ "str",
282
+ "obj",
283
+ "arr",
284
+ "gen",
285
+ "def",
286
+ "raw",
287
+ "new",
288
+ "old",
289
+ "pre",
290
+ "post",
291
+ "sub",
292
+ "pub",
293
+ // Programming/Framework specific
294
+ "ts",
295
+ "js",
296
+ "jsx",
297
+ "tsx",
298
+ "py",
299
+ "rb",
300
+ "vue",
301
+ "re",
302
+ "fn",
303
+ "fns",
304
+ "mod",
305
+ "opts",
306
+ "dev",
307
+ // Cloud/Infrastructure
308
+ "s3",
309
+ "ec2",
310
+ "sqs",
311
+ "sns",
312
+ "vpc",
313
+ "ami",
314
+ "iam",
315
+ "acl",
316
+ "elb",
317
+ "alb",
318
+ "nlb",
319
+ "aws",
320
+ "ses",
321
+ "gst",
322
+ "cdk",
323
+ "btn",
324
+ "buf",
325
+ "agg",
326
+ "ocr",
327
+ "ai",
328
+ "cf",
329
+ "cfn",
330
+ "ga",
331
+ // Metrics/Performance
332
+ "fcp",
333
+ "lcp",
334
+ "cls",
335
+ "ttfb",
336
+ "tti",
337
+ "fid",
338
+ "fps",
339
+ "qps",
340
+ "rps",
341
+ "tps",
342
+ "wpm",
343
+ // Testing & i18n
344
+ "po",
345
+ "e2e",
346
+ "a11y",
347
+ "i18n",
348
+ "l10n",
349
+ "spy",
350
+ // Domain-specific abbreviations (context-aware)
351
+ "sk",
352
+ "fy",
353
+ "faq",
354
+ "og",
355
+ "seo",
356
+ "cta",
357
+ "roi",
358
+ "kpi",
359
+ "ttl",
360
+ "pct",
361
+ // Technical abbreviations
362
+ "mac",
363
+ "hex",
364
+ "esm",
365
+ "git",
366
+ "rec",
367
+ "loc",
368
+ "dup",
369
+ // Boolean helpers (these are intentional short names)
370
+ "is",
371
+ "has",
372
+ "can",
373
+ "did",
374
+ "was",
375
+ "are",
376
+ // Date/Time context (when in date contexts)
377
+ "d",
378
+ "t",
379
+ "dt",
380
+ // Coverage metrics (industry standard: statements/branches/functions/lines)
381
+ "s",
382
+ "b",
383
+ "f",
384
+ "l",
385
+ // Common media/content abbreviations
386
+ "vid",
387
+ "pic",
388
+ "img",
389
+ "doc",
390
+ "msg"
391
+ ]);
392
+ async function analyzeNaming(files) {
393
+ const issues = [];
394
+ const rootDir = files.length > 0 ? dirname(files[0]) : process.cwd();
395
+ const config = loadConfig(rootDir);
396
+ const consistencyConfig = config?.tools?.["consistency"];
397
+ const customAbbreviations = new Set(consistencyConfig?.acceptedAbbreviations || []);
398
+ const customShortWords = new Set(consistencyConfig?.shortWords || []);
399
+ const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
400
+ for (const file of files) {
401
+ const content = await readFileContent(file);
402
+ const fileIssues = analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks);
403
+ issues.push(...fileIssues);
404
+ }
405
+ return issues;
406
+ }
407
+ function analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks) {
408
+ const issues = [];
409
+ const isTestFile = file.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/);
410
+ const lines = content.split("\n");
411
+ const allAbbreviations = /* @__PURE__ */ new Set([...ACCEPTABLE_ABBREVIATIONS, ...customAbbreviations]);
412
+ const allShortWords = /* @__PURE__ */ new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
413
+ const getContextWindow = (index, windowSize = 3) => {
414
+ const start = Math.max(0, index - windowSize);
415
+ const end = Math.min(lines.length, index + windowSize + 1);
416
+ return lines.slice(start, end).join("\n");
417
+ };
418
+ const isShortLivedVariable = (varName, declarationIndex) => {
419
+ const searchRange = 5;
420
+ const endIndex = Math.min(lines.length, declarationIndex + searchRange + 1);
421
+ let usageCount = 0;
422
+ for (let i = declarationIndex; i < endIndex; i++) {
423
+ const regex = new RegExp(`\\b${varName}\\b`, "g");
424
+ const matches = lines[i].match(regex);
425
+ if (matches) {
426
+ usageCount += matches.length;
427
+ }
428
+ }
429
+ return usageCount >= 2 && usageCount <= 3;
430
+ };
431
+ lines.forEach((line, index) => {
432
+ const lineNumber = index + 1;
433
+ const contextWindow = getContextWindow(index);
434
+ if (!disabledChecks.has("single-letter")) {
435
+ const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
436
+ for (const match of singleLetterMatches) {
437
+ const letter = match[1].toLowerCase();
438
+ const isCoverageContext = /coverage|summary|metrics|pct|percent/i.test(line) || /\.(?:statements|branches|functions|lines)\.pct/i.test(line);
439
+ if (isCoverageContext && ["s", "b", "f", "l"].includes(letter)) {
440
+ continue;
441
+ }
442
+ const isInLoopContext = line.includes("for") || /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) || line.includes("=>") || // Arrow function
443
+ /\w+\s*=>\s*/.test(line);
444
+ const isI18nContext = line.includes("useTranslation") || line.includes("i18n.t") || /\bt\s*\(['"]/.test(line);
445
+ const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
446
+ /[a-z]\s*=>/.test(line) || // s => on same line
447
+ // Multi-line arrow function detection: look for pattern in context window
448
+ new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
449
+ new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && /=>/.test(contextWindow);
450
+ const isShortLived = isShortLivedVariable(letter, index);
451
+ if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !isShortLived && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
452
+ if (isTestFile && ["a", "b", "c", "d", "e", "f", "s"].includes(letter)) {
453
+ continue;
454
+ }
455
+ issues.push({
456
+ file,
457
+ line: lineNumber,
458
+ type: "poor-naming",
459
+ identifier: match[1],
460
+ severity: "minor",
461
+ suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
462
+ });
463
+ }
464
+ }
465
+ }
466
+ if (!disabledChecks.has("abbreviation")) {
467
+ const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
468
+ for (const match of abbreviationMatches) {
469
+ const abbrev = match[1].toLowerCase();
470
+ if (allShortWords.has(abbrev)) {
471
+ continue;
472
+ }
473
+ if (allAbbreviations.has(abbrev)) {
474
+ continue;
475
+ }
476
+ const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
477
+ new RegExp(`\\b${abbrev}\\s*=>`).test(line) || // s => on same line
478
+ // Multi-line arrow function: check context window
479
+ new RegExp(`\\b${abbrev}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
480
+ new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && new RegExp(`^\\s*${abbrev}\\s*=>`).test(line);
481
+ if (isArrowFunctionParam) {
482
+ continue;
483
+ }
484
+ if (abbrev.length <= 2) {
485
+ const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
486
+ if (isDateTimeContext && ["d", "t", "dt"].includes(abbrev)) {
487
+ continue;
488
+ }
489
+ const isUserContext = /user|auth|account/i.test(line);
490
+ if (isUserContext && abbrev === "u") {
491
+ continue;
492
+ }
493
+ }
494
+ issues.push({
495
+ file,
496
+ line: lineNumber,
497
+ type: "abbreviation",
498
+ identifier: match[1],
499
+ severity: "info",
500
+ suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
501
+ });
502
+ }
503
+ }
504
+ if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
505
+ const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
506
+ const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
507
+ if (snakeCaseVars) {
508
+ issues.push({
509
+ file,
510
+ line: lineNumber,
511
+ type: "convention-mix",
512
+ identifier: snakeCaseVars[1],
513
+ severity: "minor",
514
+ suggestion: `Use camelCase '${snakeCaseToCamelCase(snakeCaseVars[1])}' instead of snake_case in TypeScript/JavaScript`
515
+ });
516
+ }
517
+ }
518
+ if (!disabledChecks.has("unclear")) {
519
+ const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
520
+ for (const match of booleanMatches) {
521
+ const name = match[1];
522
+ if (!name.match(/^(is|has|should|can|will|did)/i)) {
523
+ issues.push({
524
+ file,
525
+ line: lineNumber,
526
+ type: "unclear",
527
+ identifier: name,
528
+ severity: "info",
529
+ suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
530
+ });
531
+ }
532
+ }
533
+ }
534
+ if (!disabledChecks.has("unclear")) {
535
+ const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
536
+ for (const match of functionMatches) {
537
+ const name = match[1];
538
+ const isKeyword = ["for", "if", "else", "while", "do", "switch", "case", "break", "continue", "return", "throw", "try", "catch", "finally", "with", "yield", "await"].includes(name);
539
+ if (isKeyword) {
540
+ continue;
541
+ }
542
+ const isEntryPoint = ["main", "init", "setup", "bootstrap"].includes(name);
543
+ if (isEntryPoint) {
544
+ continue;
545
+ }
546
+ const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator|Provider|Adapter|Mock)$/);
547
+ const isEventHandler = name.match(/^on[A-Z]/);
548
+ const isDescriptiveLong = name.length > 15;
549
+ const isReactHook = name.match(/^use[A-Z]/);
550
+ const isDescriptivePattern = name.match(/^(default|total|count|sum|avg|max|min|initial|current|previous|next)\w+/) || name.match(/\w+(Count|Total|Sum|Average|List|Map|Set|Config|Settings|Options|Props|Data|Info|Details|State|Status|Response|Result)$/);
551
+ const isHelperPattern = name.match(/^(to|from|with|without|for|as|into)\w+/) || // toMetadata, withLogger, forPath
552
+ name.match(/^\w+(To|From|With|Without|For|As|Into)\w*$/);
553
+ const isUtilityName = ["cn", "proxy", "sitemap", "robots", "gtag"].includes(name);
554
+ const capitalCount = (name.match(/[A-Z]/g) || []).length;
555
+ const isCompoundWord = capitalCount >= 3;
556
+ const hasActionVerb = name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce|make|do|run|start|stop|build|parse|format|render|calculate|compute|generate|transform|convert|normalize|sanitize|encode|decode|compress|extract|merge|split|join|sort|compare|test|verify|ensure|apply|execute|invoke|call|emit|dispatch|trigger|listen|subscribe|unsubscribe|add|remove|clear|reset|toggle|enable|disable|open|close|connect|disconnect|send|receive|read|write|import|export|register|unregister|mount|unmount|track|store|persist|upsert|derive|classify|combine|discover|activate|require|assert|expect|mask|escape|sign|put|list|complete|page|safe|mock|pick|pluralize|text)/);
557
+ if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord && !isHelperPattern && !isUtilityName && !isReactHook) {
558
+ issues.push({
559
+ file,
560
+ line: lineNumber,
561
+ type: "unclear",
562
+ identifier: name,
563
+ severity: "info",
564
+ suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
565
+ });
566
+ }
567
+ }
568
+ }
569
+ });
570
+ return issues;
571
+ }
572
+ function snakeCaseToCamelCase(str) {
573
+ return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
574
+ }
575
+ function detectNamingConventions(files, allIssues) {
576
+ const camelCaseCount = allIssues.filter((i) => i.type === "convention-mix").length;
577
+ const totalChecks = files.length * 10;
578
+ if (camelCaseCount / totalChecks > 0.3) {
579
+ return { dominantConvention: "mixed", conventionScore: 0.5 };
580
+ }
581
+ return { dominantConvention: "camelCase", conventionScore: 0.9 };
582
+ }
7
583
  export {
8
584
  analyzeConsistency,
9
585
  analyzeNaming,
586
+ analyzeNamingAST,
10
587
  analyzePatterns,
11
588
  detectNamingConventions
12
589
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/consistency",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "Detects consistency issues in naming, patterns, and architecture that confuse AI models",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -15,15 +15,6 @@
15
15
  "import": "./dist/index.mjs"
16
16
  }
17
17
  },
18
- "scripts": {
19
- "build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts",
20
- "dev": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --watch",
21
- "test": "vitest run",
22
- "lint": "eslint src",
23
- "clean": "rm -rf dist",
24
- "prepublishOnly": "pnpm build",
25
- "release": "pnpm build && pnpm publish --no-git-checks"
26
- },
27
18
  "keywords": [
28
19
  "aiready",
29
20
  "consistency",
@@ -48,9 +39,11 @@
48
39
  },
49
40
  "homepage": "https://github.com/caopengau/aiready-consistency",
50
41
  "dependencies": {
51
- "@aiready/core": "workspace:*",
42
+ "@typescript-eslint/types": "^8.53.0",
43
+ "@typescript-eslint/typescript-estree": "^8.53.0",
52
44
  "chalk": "^5.3.0",
53
- "commander": "^12.1.0"
45
+ "commander": "^12.1.0",
46
+ "@aiready/core": "0.7.1"
54
47
  },
55
48
  "devDependencies": {
56
49
  "@types/node": "^22.10.5",
@@ -60,5 +53,13 @@
60
53
  },
61
54
  "engines": {
62
55
  "node": ">=18.0.0"
56
+ },
57
+ "scripts": {
58
+ "build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts",
59
+ "dev": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --watch",
60
+ "test": "vitest run",
61
+ "lint": "eslint src",
62
+ "clean": "rm -rf dist",
63
+ "release": "pnpm build && pnpm publish --no-git-checks"
63
64
  }
64
- }
65
+ }
package/src/analyzer.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { scanFiles } from '@aiready/core';
2
2
  import type { AnalysisResult, Issue } from '@aiready/core';
3
3
  import type { ConsistencyOptions, ConsistencyReport, ConsistencyIssue } from './types';
4
- import { analyzeNaming, detectNamingConventions } from './analyzers/naming';
4
+ import { analyzeNamingAST } from './analyzers/naming-ast';
5
5
  import { analyzePatterns } from './analyzers/patterns';
6
6
 
7
7
  /**
@@ -22,7 +22,7 @@ export async function analyzeConsistency(
22
22
  const filePaths = await scanFiles(scanOptions);
23
23
 
24
24
  // Collect issues by category
25
- const namingIssues = checkNaming ? await analyzeNaming(filePaths) : [];
25
+ const namingIssues = checkNaming ? await analyzeNamingAST(filePaths) : [];
26
26
  const patternIssues = checkPatterns ? await analyzePatterns(filePaths) : [];
27
27
 
28
28
  // Convert to AnalysisResult format
@@ -97,8 +97,8 @@ export async function analyzeConsistency(
97
97
  // Generate recommendations
98
98
  const recommendations = generateRecommendations(namingIssues, patternIssues);
99
99
 
100
- // Detect naming conventions
101
- const conventionAnalysis = detectNamingConventions(filePaths, namingIssues);
100
+ // Detect naming conventions (TODO: re-implement for AST version)
101
+ // const conventionAnalysis = detectNamingConventions(filePaths, namingIssues);
102
102
 
103
103
  return {
104
104
  summary: {