@aiready/consistency 0.20.11 → 0.20.13

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.
@@ -1,24 +1,23 @@
1
-
2
- 
3
- > @aiready/consistency@0.20.11 build /Users/pengcao/projects/aiready/packages/consistency
4
- > tsup src/index.ts src/cli.ts --format cjs,esm --dts
5
-
6
- CLI Building entry: src/cli.ts, src/index.ts
7
- CLI Using tsconfig: tsconfig.json
8
- CLI tsup v8.5.1
9
- CLI Target: es2020
10
- CJS Build start
11
- ESM Build start
12
- ESM dist/index.mjs 7.04 KB
13
- ESM dist/cli.mjs 8.83 KB
14
- ESM dist/chunk-TE6JYZD3.mjs 23.58 KB
15
- ESM ⚡️ Build success in 284ms
16
- CJS dist/cli.js 34.45 KB
17
- CJS dist/index.js 32.34 KB
18
- CJS ⚡️ Build success in 284ms
19
- DTS Build start
20
- DTS ⚡️ Build success in 5562ms
21
- DTS dist/cli.d.ts 20.00 B
22
- DTS dist/index.d.ts 3.87 KB
23
- DTS dist/cli.d.mts 20.00 B
24
- DTS dist/index.d.mts 3.87 KB
1
+
2
+ > @aiready/consistency@0.20.13 build /Users/pengcao/projects/aiready/packages/consistency
3
+ > tsup src/index.ts src/cli.ts --format cjs,esm --dts
4
+
5
+ CLI Building entry: src/cli.ts, src/index.ts
6
+ CLI Using tsconfig: tsconfig.json
7
+ CLI tsup v8.5.1
8
+ CLI Target: es2020
9
+ CJS Build start
10
+ ESM Build start
11
+ CJS dist/cli.js 34.64 KB
12
+ CJS dist/index.js 32.53 KB
13
+ CJS ⚡️ Build success in 565ms
14
+ ESM dist/cli.mjs 8.83 KB
15
+ ESM dist/chunk-LSXZH6X6.mjs 23.77 KB
16
+ ESM dist/index.mjs 7.04 KB
17
+ ESM ⚡️ Build success in 584ms
18
+ DTS Build start
19
+ DTS ⚡️ Build success in 11169ms
20
+ DTS dist/cli.d.ts 20.00 B
21
+ DTS dist/index.d.ts 3.87 KB
22
+ DTS dist/cli.d.mts 20.00 B
23
+ DTS dist/index.d.mts 3.87 KB
@@ -1,28 +1,32 @@
1
-
2
- 
3
- > @aiready/consistency@0.20.10 test /Users/pengcao/projects/aiready/packages/consistency
4
- > vitest run
5
-
6
- [?25l
7
-  RUN  v4.0.18 /Users/pengcao/projects/aiready/packages/consistency
8
-
9
- ✓ dist/__tests__/scoring.test.js (8 tests) 3ms
10
- Sourcemap for "/Users/pengcao/projects/aiready/packages/consistency/dist/analyzers/naming-python.js" points to missing source files
11
- ✓ src/__tests__/scoring.test.ts (8 tests) 12ms
12
- ✓ src/__tests__/language-filter.test.ts (3 tests) 6ms
13
- ✓ dist/__tests__/language-filter.test.js (3 tests) 6ms
14
- ✓ src/__tests__/provider.test.ts (2 tests) 12ms
15
- stdout | src/__tests__/contract.test.ts > Consistency Spoke Contract Validation > should produce output matching the SpokeOutput contract
16
- Consistency: Skipping unparseable file file1.ts: ENOENT: no such file or directory, open 'file1.ts'
17
-
18
- ✓ src/__tests__/contract.test.ts (1 test) 6ms
19
- ✓ dist/__tests__/analyzer.test.js (18 tests) 438ms
20
- ✓ src/__tests__/analyzer.test.ts (18 tests) 479ms
21
- ✓ should analyze naming issues  312ms
22
-
23
-  Test Files  8 passed (8)
24
-  Tests  61 passed (61)
25
-  Start at  00:25:35
26
-  Duration  2.44s (transform 3.21s, setup 0ms, import 10.78s, tests 962ms, environment 1ms)
27
-
28
- [?25h
1
+
2
+ > @aiready/consistency@0.20.12 test /Users/pengcao/projects/aiready/packages/consistency
3
+ > vitest run
4
+
5
+
6
+  RUN  v4.0.18 /Users/pengcao/projects/aiready/packages/consistency
7
+
8
+ ✓ src/__tests__/naming-constants.test.ts (14 tests) 4ms
9
+ ✓ dist/__tests__/scoring.test.js (8 tests) 36ms
10
+ ✓ src/__tests__/language-filter.test.ts (3 tests) 3ms
11
+ Sourcemap for "/Users/pengcao/projects/aiready/packages/consistency/dist/analyzers/naming-python.js" points to missing source files
12
+ ✓ src/__tests__/scoring.test.ts (8 tests) 12ms
13
+ stdout | src/__tests__/contract.test.ts > Consistency Spoke Contract Validation > should produce output matching the SpokeOutput contract
14
+ Consistency: Skipping unparseable file file1.ts: ENOENT: no such file or directory, open 'file1.ts'
15
+
16
+ ✓ dist/__tests__/language-filter.test.js (3 tests) 7ms
17
+ ✓ src/__tests__/contract.test.ts (1 test) 22ms
18
+ ✓ src/__tests__/provider.test.ts (2 tests) 19ms
19
+ ✓ src/__tests__/analyzer.test.ts (18 tests) 2253ms
20
+ ✓ should analyze naming issues  1241ms
21
+ ✓ should detect minimum severity filtering  564ms
22
+ ✓ should generate relevant recommendations  443ms
23
+ ✓ dist/__tests__/analyzer.test.js (18 tests) 2456ms
24
+ ✓ should analyze naming issues  771ms
25
+ ✓ should detect minimum severity filtering  621ms
26
+ ✓ should generate relevant recommendations  1057ms
27
+
28
+  Test Files  9 passed (9)
29
+  Tests  75 passed (75)
30
+  Start at  10:44:29
31
+  Duration  5.77s (transform 3.42s, setup 0ms, import 20.19s, tests 4.81s, environment 3ms)
32
+
@@ -0,0 +1,810 @@
1
+ // src/analyzers/naming-ast.ts
2
+ import { Severity as Severity2 } from "@aiready/core";
3
+
4
+ // src/utils/ast-parser.ts
5
+ import { parse } from "@typescript-eslint/typescript-estree";
6
+ import { readFileSync } from "fs";
7
+ function parseFile(filePath, content) {
8
+ try {
9
+ const code = content ?? readFileSync(filePath, "utf-8");
10
+ const isTypeScript = filePath.match(/\.tsx?$/);
11
+ return parse(code, {
12
+ jsx: filePath.match(/\.[jt]sx$/i) !== null,
13
+ loc: true,
14
+ range: true,
15
+ comment: false,
16
+ tokens: false,
17
+ // Relaxed parsing for JavaScript files
18
+ sourceType: "module",
19
+ ecmaVersion: "latest",
20
+ // Only use TypeScript parser features for .ts/.tsx files
21
+ filePath: isTypeScript ? filePath : void 0
22
+ });
23
+ } catch (error) {
24
+ void error;
25
+ return null;
26
+ }
27
+ }
28
+ function traverseAST(node, visitor, parent = null) {
29
+ if (!node) return;
30
+ visitor.enter?.(node, parent);
31
+ for (const key of Object.keys(node)) {
32
+ const value = node[key];
33
+ if (Array.isArray(value)) {
34
+ for (const child of value) {
35
+ if (child && typeof child === "object" && "type" in child) {
36
+ traverseAST(child, visitor, node);
37
+ }
38
+ }
39
+ } else if (value && typeof value === "object" && "type" in value) {
40
+ traverseAST(value, visitor, node);
41
+ }
42
+ }
43
+ visitor.leave?.(node, parent);
44
+ }
45
+ function isLoopStatement(node) {
46
+ return [
47
+ "ForStatement",
48
+ "ForInStatement",
49
+ "ForOfStatement",
50
+ "WhileStatement",
51
+ "DoWhileStatement"
52
+ ].includes(node.type);
53
+ }
54
+ function getLineNumber(node) {
55
+ return node.loc?.start.line ?? 0;
56
+ }
57
+
58
+ // src/utils/context-detector.ts
59
+ import { Severity } from "@aiready/core";
60
+ function detectFileType(filePath, ast) {
61
+ void ast;
62
+ const path = filePath.toLowerCase();
63
+ if (path.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/) || path.includes("__tests__")) {
64
+ return "test";
65
+ }
66
+ if (path.endsWith(".d.ts") || path.includes("types")) {
67
+ return "types";
68
+ }
69
+ if (path.match(/config|\.config\.|rc\.|setup/) || path.includes("configuration")) {
70
+ return "config";
71
+ }
72
+ return "production";
73
+ }
74
+ function detectCodeLayer(ast) {
75
+ let hasAPIIndicators = 0;
76
+ let hasBusinessIndicators = 0;
77
+ let hasDataIndicators = 0;
78
+ let hasUtilityIndicators = 0;
79
+ traverseAST(ast, {
80
+ enter: (node) => {
81
+ if (node.type === "ImportDeclaration") {
82
+ const source = node.source.value;
83
+ if (source.match(/express|fastify|koa|@nestjs|axios|fetch|http/i)) {
84
+ hasAPIIndicators++;
85
+ }
86
+ if (source.match(/database|prisma|typeorm|sequelize|mongoose|pg|mysql/i)) {
87
+ hasDataIndicators++;
88
+ }
89
+ }
90
+ if (node.type === "FunctionDeclaration" && node.id) {
91
+ const name = node.id.name;
92
+ if (name.match(
93
+ /^(get|post|put|delete|patch|handle|api|route|controller)/i
94
+ )) {
95
+ hasAPIIndicators++;
96
+ }
97
+ if (name.match(/^(calculate|process|validate|transform|compute|analyze)/i)) {
98
+ hasBusinessIndicators++;
99
+ }
100
+ if (name.match(/^(find|create|update|delete|save|fetch|query|insert)/i)) {
101
+ hasDataIndicators++;
102
+ }
103
+ if (name.match(
104
+ /^(format|parse|convert|normalize|sanitize|encode|decode)/i
105
+ )) {
106
+ hasUtilityIndicators++;
107
+ }
108
+ }
109
+ if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") {
110
+ if (node.type === "ExportNamedDeclaration" && node.declaration) {
111
+ if (node.declaration.type === "FunctionDeclaration" && node.declaration.id) {
112
+ const name = node.declaration.id.name;
113
+ if (name.match(/handler|route|api|controller/i)) {
114
+ hasAPIIndicators += 2;
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
120
+ });
121
+ const scores = {
122
+ api: hasAPIIndicators,
123
+ business: hasBusinessIndicators,
124
+ data: hasDataIndicators,
125
+ utility: hasUtilityIndicators
126
+ };
127
+ const maxScore = Math.max(...Object.values(scores));
128
+ if (maxScore === 0) {
129
+ return "unknown";
130
+ }
131
+ if (scores.api === maxScore) return "api";
132
+ if (scores.data === maxScore) return "data";
133
+ if (scores.business === maxScore) return "business";
134
+ if (scores.utility === maxScore) return "utility";
135
+ return "unknown";
136
+ }
137
+ function calculateComplexity(node) {
138
+ let complexity = 1;
139
+ traverseAST(node, {
140
+ enter: (childNode) => {
141
+ switch (childNode.type) {
142
+ case "IfStatement":
143
+ case "ConditionalExpression":
144
+ // ternary
145
+ case "SwitchCase":
146
+ case "ForStatement":
147
+ case "ForInStatement":
148
+ case "ForOfStatement":
149
+ case "WhileStatement":
150
+ case "DoWhileStatement":
151
+ case "CatchClause":
152
+ complexity++;
153
+ break;
154
+ case "LogicalExpression":
155
+ if (childNode.operator === "&&" || childNode.operator === "||") {
156
+ complexity++;
157
+ }
158
+ break;
159
+ }
160
+ }
161
+ });
162
+ return complexity;
163
+ }
164
+ function buildCodeContext(filePath, ast) {
165
+ const fileType = detectFileType(filePath, ast);
166
+ const codeLayer = detectCodeLayer(ast);
167
+ let totalComplexity = 0;
168
+ let functionCount = 0;
169
+ traverseAST(ast, {
170
+ enter: (node) => {
171
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
172
+ totalComplexity += calculateComplexity(node);
173
+ functionCount++;
174
+ }
175
+ }
176
+ });
177
+ const avgComplexity = functionCount > 0 ? totalComplexity / functionCount : 1;
178
+ return {
179
+ fileType,
180
+ codeLayer,
181
+ complexity: Math.round(avgComplexity),
182
+ isTestFile: fileType === "test",
183
+ isTypeDefinition: fileType === "types"
184
+ };
185
+ }
186
+ function adjustSeverity(baseSeverity, context, issueType) {
187
+ const getEnum = (s) => {
188
+ if (s === Severity.Critical || s === "critical") return Severity.Critical;
189
+ if (s === Severity.Major || s === "major") return Severity.Major;
190
+ if (s === Severity.Minor || s === "minor") return Severity.Minor;
191
+ return Severity.Info;
192
+ };
193
+ let currentSev = getEnum(baseSeverity);
194
+ if (context.isTestFile) {
195
+ if (currentSev === Severity.Minor) currentSev = Severity.Info;
196
+ if (currentSev === Severity.Major) currentSev = Severity.Minor;
197
+ }
198
+ if (context.isTypeDefinition) {
199
+ if (currentSev === Severity.Minor) currentSev = Severity.Info;
200
+ }
201
+ if (context.codeLayer === "api") {
202
+ if (currentSev === Severity.Info && issueType === "unclear")
203
+ currentSev = Severity.Minor;
204
+ if (currentSev === Severity.Minor && issueType === "unclear")
205
+ currentSev = Severity.Major;
206
+ }
207
+ if (context.complexity > 10) {
208
+ if (currentSev === Severity.Info) currentSev = Severity.Minor;
209
+ }
210
+ if (context.codeLayer === "utility") {
211
+ if (currentSev === Severity.Minor && issueType === "abbreviation")
212
+ currentSev = Severity.Info;
213
+ }
214
+ return currentSev;
215
+ }
216
+ function isAcceptableInContext(name, context, options) {
217
+ if (options.isLoopVariable && ["i", "j", "k", "l", "n", "m"].includes(name)) {
218
+ return true;
219
+ }
220
+ if (context.isTestFile) {
221
+ if (["a", "b", "c", "x", "y", "z"].includes(name) && options.isParameter) {
222
+ return true;
223
+ }
224
+ }
225
+ if (context.codeLayer === "utility" && ["x", "y", "z"].includes(name)) {
226
+ return true;
227
+ }
228
+ if (options.isDestructured) {
229
+ if (["s", "b", "f", "l"].includes(name)) {
230
+ return true;
231
+ }
232
+ }
233
+ if (options.isParameter && (options.complexity ?? context.complexity) < 3) {
234
+ if (name.length >= 2) {
235
+ return true;
236
+ }
237
+ }
238
+ return false;
239
+ }
240
+
241
+ // src/analyzers/naming-ast.ts
242
+ async function analyzeNamingAST(filePaths) {
243
+ const allIssues = [];
244
+ for (const filePath of filePaths) {
245
+ try {
246
+ const ast = parseFile(filePath);
247
+ if (!ast) continue;
248
+ const context = buildCodeContext(filePath, ast);
249
+ const issues = analyzeIdentifiers(ast, filePath, context);
250
+ allIssues.push(...issues);
251
+ } catch (err) {
252
+ void err;
253
+ }
254
+ }
255
+ return allIssues;
256
+ }
257
+ function analyzeIdentifiers(ast, filePath, context) {
258
+ const issues = [];
259
+ const scopeTracker = new ScopeTracker();
260
+ traverseAST(ast, {
261
+ enter: (node) => {
262
+ if (node.type === "VariableDeclarator" && node.id.type === "Identifier") {
263
+ const isParameter = false;
264
+ const isLoopVariable = isLoopStatement(node.parent?.parent);
265
+ scopeTracker.declareVariable(
266
+ node.id.name,
267
+ node.id,
268
+ getLineNumber(node.id),
269
+ { isParameter, isLoopVariable }
270
+ );
271
+ }
272
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
273
+ node.params.forEach((param) => {
274
+ if (param.type === "Identifier") {
275
+ scopeTracker.declareVariable(
276
+ param.name,
277
+ param,
278
+ getLineNumber(param),
279
+ { isParameter: true }
280
+ );
281
+ } else if (param.type === "ObjectPattern") {
282
+ extractDestructuredIdentifiers(param, true, scopeTracker);
283
+ }
284
+ });
285
+ }
286
+ if ((node.type === "ClassDeclaration" || node.type === "TSInterfaceDeclaration" || node.type === "TSTypeAliasDeclaration") && node.id) {
287
+ checkNamingConvention(
288
+ node.id.name,
289
+ "PascalCase",
290
+ node.id,
291
+ filePath,
292
+ issues,
293
+ context
294
+ );
295
+ }
296
+ }
297
+ });
298
+ for (const varInfo of scopeTracker.getVariables()) {
299
+ checkVariableNaming(varInfo, filePath, issues, context);
300
+ }
301
+ return issues;
302
+ }
303
+ function checkNamingConvention(name, convention, node, file, issues, context) {
304
+ let isValid = true;
305
+ if (convention === "PascalCase") {
306
+ isValid = /^[A-Z][a-zA-Z0-9]*$/.test(name);
307
+ } else if (convention === "camelCase") {
308
+ isValid = /^[a-z][a-zA-Z0-9]*$/.test(name);
309
+ } else if (convention === "UPPER_CASE") {
310
+ isValid = /^[A-Z][A-Z0-9_]*$/.test(name);
311
+ }
312
+ if (!isValid) {
313
+ const severity = adjustSeverity(Severity2.Info, context, "convention-mix");
314
+ issues.push({
315
+ file,
316
+ line: getLineNumber(node),
317
+ type: "convention-mix",
318
+ identifier: name,
319
+ severity,
320
+ suggestion: `Follow ${convention} for this identifier`
321
+ });
322
+ }
323
+ }
324
+ function checkVariableNaming(varInfo, file, issues, context) {
325
+ const { name, line, options } = varInfo;
326
+ if (isAcceptableInContext(name, context, options)) {
327
+ return;
328
+ }
329
+ if (name.length === 1 && !options.isLoopVariable) {
330
+ const severity = adjustSeverity(Severity2.Minor, context, "poor-naming");
331
+ issues.push({
332
+ file,
333
+ line,
334
+ type: "poor-naming",
335
+ identifier: name,
336
+ severity,
337
+ suggestion: "Use a more descriptive name than a single letter"
338
+ });
339
+ }
340
+ const vagueNames = [
341
+ "data",
342
+ "info",
343
+ "item",
344
+ "obj",
345
+ "val",
346
+ "tmp",
347
+ "temp",
348
+ "thing",
349
+ "stuff"
350
+ ];
351
+ if (vagueNames.includes(name.toLowerCase())) {
352
+ const severity = adjustSeverity(Severity2.Minor, context, "poor-naming");
353
+ issues.push({
354
+ file,
355
+ line,
356
+ type: "poor-naming",
357
+ identifier: name,
358
+ severity,
359
+ suggestion: `Avoid vague names like '${name}'. What does this data represent?`
360
+ });
361
+ }
362
+ if (name.length > 1 && name.length <= 3 && !options.isLoopVariable && !isCommonAbbreviation(name)) {
363
+ const severity = adjustSeverity(Severity2.Info, context, "abbreviation");
364
+ issues.push({
365
+ file,
366
+ line,
367
+ type: "abbreviation",
368
+ identifier: name,
369
+ severity,
370
+ suggestion: "Avoid non-standard abbreviations"
371
+ });
372
+ }
373
+ }
374
+ function isCommonAbbreviation(name) {
375
+ const common = [
376
+ "id",
377
+ "db",
378
+ "fs",
379
+ "os",
380
+ "ip",
381
+ "ui",
382
+ "ux",
383
+ "api",
384
+ "env",
385
+ "url",
386
+ "req",
387
+ "res",
388
+ "err",
389
+ "ctx",
390
+ "cb",
391
+ "idx",
392
+ "src",
393
+ "dir",
394
+ "app",
395
+ "dev",
396
+ "qa",
397
+ "dto",
398
+ "dao",
399
+ "ref",
400
+ "ast",
401
+ "dom",
402
+ "log",
403
+ "msg",
404
+ "pkg",
405
+ "req",
406
+ "err",
407
+ "res",
408
+ "css",
409
+ "html",
410
+ "xml",
411
+ "jsx",
412
+ "tsx",
413
+ "ts",
414
+ "js"
415
+ ];
416
+ return common.includes(name.toLowerCase());
417
+ }
418
+ var ScopeTracker = class {
419
+ constructor() {
420
+ this.variables = [];
421
+ }
422
+ declareVariable(name, node, line, options = {}) {
423
+ this.variables.push({ name, node, line, options });
424
+ }
425
+ getVariables() {
426
+ return this.variables;
427
+ }
428
+ };
429
+ function extractDestructuredIdentifiers(node, isParameter, scopeTracker) {
430
+ if (node.type === "ObjectPattern") {
431
+ node.properties.forEach((prop) => {
432
+ if (prop.type === "Property" && prop.value.type === "Identifier") {
433
+ scopeTracker.declareVariable(
434
+ prop.value.name,
435
+ prop.value,
436
+ getLineNumber(prop.value),
437
+ {
438
+ isParameter,
439
+ isDestructured: true
440
+ }
441
+ );
442
+ }
443
+ });
444
+ } else if (node.type === "ArrayPattern") {
445
+ for (const element of node.elements) {
446
+ if (element?.type === "Identifier") {
447
+ scopeTracker.declareVariable(
448
+ element.name,
449
+ element,
450
+ getLineNumber(element),
451
+ {
452
+ isParameter,
453
+ isDestructured: true
454
+ }
455
+ );
456
+ }
457
+ }
458
+ }
459
+ }
460
+
461
+ // src/analyzers/patterns.ts
462
+ import { readFileSync as readFileSync2 } from "fs";
463
+ import { Severity as Severity3 } from "@aiready/core";
464
+ async function analyzePatterns(filePaths) {
465
+ const issues = [];
466
+ const contents = /* @__PURE__ */ new Map();
467
+ const tryCatchPattern = /try\s*\{/g;
468
+ const styleStats = {
469
+ tryCatch: 0,
470
+ thenCatch: 0,
471
+ asyncAwait: 0,
472
+ commonJs: 0,
473
+ esm: 0
474
+ };
475
+ for (const filePath of filePaths) {
476
+ try {
477
+ const content = readFileSync2(filePath, "utf-8");
478
+ contents.set(filePath, content);
479
+ if (content.match(tryCatchPattern)) styleStats.tryCatch++;
480
+ if (content.match(/\.catch\s*\(/)) styleStats.thenCatch++;
481
+ if (content.match(/\bawait\b/)) styleStats.asyncAwait++;
482
+ if (content.match(/\brequire\s*\(/)) styleStats.commonJs++;
483
+ if (content.match(/\bimport\b.*\bfrom\b/)) styleStats.esm++;
484
+ } catch (err) {
485
+ void err;
486
+ }
487
+ }
488
+ if (styleStats.tryCatch > 0 && styleStats.thenCatch > 0) {
489
+ const dominant = styleStats.tryCatch >= styleStats.thenCatch ? "try-catch" : ".catch()";
490
+ const minority = dominant === "try-catch" ? ".catch()" : "try-catch";
491
+ issues.push({
492
+ files: filePaths.filter((f) => {
493
+ const c = contents.get(f) || "";
494
+ return minority === "try-catch" ? c.match(tryCatchPattern) : c.match(/\.catch\s*\(/);
495
+ }),
496
+ type: "pattern-inconsistency",
497
+ description: `Mixed error handling styles: codebase primarily uses ${dominant}, but found ${minority} in some files.`,
498
+ examples: [dominant, minority],
499
+ severity: Severity3.Minor
500
+ });
501
+ }
502
+ if (styleStats.commonJs > 0 && styleStats.esm > 0) {
503
+ const minority = styleStats.esm >= styleStats.commonJs ? "CommonJS (require)" : "ESM (import)";
504
+ issues.push({
505
+ files: filePaths.filter((f) => {
506
+ const c = contents.get(f) || "";
507
+ return minority === "CommonJS (require)" ? c.match(/\brequire\s*\(/) : c.match(/\bimport\b/);
508
+ }),
509
+ type: "pattern-inconsistency",
510
+ description: `Mixed module systems: found both ESM and CommonJS.`,
511
+ examples: ['import X from "y"', 'const X = require("y")'],
512
+ severity: Severity3.Major
513
+ });
514
+ }
515
+ return issues;
516
+ }
517
+
518
+ // src/analyzer.ts
519
+ import {
520
+ scanFiles,
521
+ Severity as Severity5,
522
+ IssueType,
523
+ getSeverityLevel
524
+ } from "@aiready/core";
525
+
526
+ // src/analyzers/naming-generalized.ts
527
+ import { getParser, Severity as Severity4 } from "@aiready/core";
528
+ import { readFileSync as readFileSync3 } from "fs";
529
+ var COMMON_ABBREVIATIONS = /* @__PURE__ */ new Set([
530
+ "id",
531
+ "db",
532
+ "fs",
533
+ "os",
534
+ "ip",
535
+ "ui",
536
+ "ux",
537
+ "api",
538
+ "env",
539
+ "url",
540
+ "req",
541
+ "res",
542
+ "err",
543
+ "ctx",
544
+ "cb",
545
+ "idx",
546
+ "src",
547
+ "dir",
548
+ "app",
549
+ "dev",
550
+ "qa",
551
+ "dto",
552
+ "dao",
553
+ "ref",
554
+ "ast",
555
+ "dom",
556
+ "log",
557
+ "msg",
558
+ "pkg",
559
+ "css",
560
+ "html",
561
+ "xml",
562
+ "jsx",
563
+ "tsx",
564
+ "ts",
565
+ "js"
566
+ ]);
567
+ async function analyzeNamingGeneralized(files) {
568
+ const issues = [];
569
+ for (const file of files) {
570
+ const parser = getParser(file);
571
+ if (!parser) continue;
572
+ try {
573
+ const code = readFileSync3(file, "utf-8");
574
+ if (!code.trim()) continue;
575
+ await parser.initialize();
576
+ const result = parser.parse(code, file);
577
+ const conventions = parser.getNamingConventions();
578
+ const exceptions = new Set(conventions.exceptions || []);
579
+ for (const exp of result.exports) {
580
+ if (!exp.name || exp.name === "default") continue;
581
+ if (exceptions.has(exp.name)) continue;
582
+ if (COMMON_ABBREVIATIONS.has(exp.name.toLowerCase())) continue;
583
+ let pattern;
584
+ if (exp.type === "class") {
585
+ pattern = conventions.classPattern;
586
+ } else if (exp.type === "interface" && conventions.interfacePattern) {
587
+ pattern = conventions.interfacePattern;
588
+ } else if (exp.type === "type" && conventions.typePattern) {
589
+ pattern = conventions.typePattern;
590
+ } else if (exp.type === "function") {
591
+ pattern = conventions.functionPattern;
592
+ } else if (exp.type === "const") {
593
+ pattern = exp.isPrimitive ? conventions.constantPattern : conventions.variablePattern;
594
+ } else {
595
+ pattern = conventions.variablePattern;
596
+ }
597
+ if (pattern && !pattern.test(exp.name)) {
598
+ issues.push({
599
+ type: "naming-inconsistency",
600
+ identifier: exp.name,
601
+ file,
602
+ line: exp.loc?.start.line || 1,
603
+ column: exp.loc?.start.column || 0,
604
+ // Recalibrate naming issues to Minor to differentiate from structural/architectural issues
605
+ severity: Severity4.Minor,
606
+ category: "naming",
607
+ suggestion: `Follow ${parser.language} ${exp.type} naming convention: ${pattern.toString()}`
608
+ });
609
+ }
610
+ }
611
+ for (const imp of result.imports) {
612
+ for (const spec of imp.specifiers) {
613
+ if (!spec || spec === "*" || spec === "default") continue;
614
+ if (exceptions.has(spec)) continue;
615
+ if (COMMON_ABBREVIATIONS.has(spec.toLowerCase())) continue;
616
+ if (!conventions.variablePattern.test(spec) && !conventions.classPattern.test(spec) && !conventions.constantPattern.test(spec) && (!conventions.typePattern || !conventions.typePattern.test(spec)) && (!conventions.interfacePattern || !conventions.interfacePattern.test(spec))) {
617
+ issues.push({
618
+ type: "naming-inconsistency",
619
+ identifier: spec,
620
+ file,
621
+ line: imp.loc?.start.line || 1,
622
+ column: imp.loc?.start.column || 0,
623
+ severity: Severity4.Info,
624
+ // Reduced from Minor to Info for imports
625
+ category: "naming",
626
+ suggestion: `Imported identifier '${spec}' may not follow standard conventions for this language.`
627
+ });
628
+ }
629
+ }
630
+ }
631
+ } catch (error) {
632
+ const errorMessage = error instanceof Error ? error.message : String(error);
633
+ console.debug(
634
+ `Consistency: Skipping unparseable file ${file}: ${errorMessage.split("\\n")[0]}`
635
+ );
636
+ }
637
+ }
638
+ return issues;
639
+ }
640
+
641
+ // src/analyzer.ts
642
+ async function analyzeConsistency(options) {
643
+ const {
644
+ checkNaming = true,
645
+ checkPatterns = true,
646
+ checkArchitecture = false,
647
+ // Not implemented yet
648
+ minSeverity = Severity5.Info,
649
+ ...scanOptions
650
+ } = options;
651
+ void checkArchitecture;
652
+ const filePaths = await scanFiles(scanOptions);
653
+ let namingIssues = [];
654
+ if (checkNaming) {
655
+ namingIssues = await analyzeNamingGeneralized(filePaths);
656
+ const tsJsFiles = filePaths.filter((f) => /\.(ts|tsx|js|jsx)$/i.test(f));
657
+ if (tsJsFiles.length > 0) {
658
+ const deepTsIssues = await analyzeNamingAST(tsJsFiles);
659
+ namingIssues = [...namingIssues, ...deepTsIssues];
660
+ }
661
+ }
662
+ const patternIssues = checkPatterns ? await analyzePatterns(filePaths) : [];
663
+ const results = [];
664
+ const fileIssuesMap = /* @__PURE__ */ new Map();
665
+ for (const issue of namingIssues) {
666
+ if (!shouldIncludeSeverity(issue.severity, minSeverity)) continue;
667
+ const fileName = issue.fileName || issue.file || issue.filePath || "unknown";
668
+ if (!fileIssuesMap.has(fileName)) fileIssuesMap.set(fileName, []);
669
+ fileIssuesMap.get(fileName).push(issue);
670
+ }
671
+ for (const issue of patternIssues) {
672
+ if (!shouldIncludeSeverity(issue.severity, minSeverity)) continue;
673
+ const fileName = issue.fileName || issue.file || issue.filePath || (Array.isArray(issue.files) ? issue.files[0] : "unknown");
674
+ if (!fileIssuesMap.has(fileName)) fileIssuesMap.set(fileName, []);
675
+ fileIssuesMap.get(fileName).push(issue);
676
+ }
677
+ for (const [fileName, issues] of fileIssuesMap.entries()) {
678
+ results.push({
679
+ fileName,
680
+ issues: issues.map((i) => transformToIssue(i)),
681
+ metrics: {
682
+ consistencyScore: calculateConsistencyScore(issues)
683
+ }
684
+ });
685
+ }
686
+ const recommendations = [];
687
+ if (namingIssues.length > 0) {
688
+ recommendations.push("Standardize naming conventions across the codebase");
689
+ }
690
+ if (patternIssues.length > 0) {
691
+ recommendations.push("Consolidate repetitive implementation patterns");
692
+ }
693
+ if (results.some((r) => (r.metrics?.consistencyScore ?? 1) < 0.8)) {
694
+ recommendations.push(
695
+ "Improve cross-module consistency to reduce AI confusion"
696
+ );
697
+ }
698
+ return {
699
+ results,
700
+ summary: {
701
+ filesAnalyzed: filePaths.length,
702
+ totalIssues: results.reduce((acc, r) => acc + r.issues.length, 0),
703
+ namingIssues: namingIssues.length,
704
+ patternIssues: patternIssues.length,
705
+ architectureIssues: 0
706
+ },
707
+ recommendations,
708
+ metadata: {
709
+ toolName: "naming-consistency",
710
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
711
+ }
712
+ };
713
+ }
714
+ function shouldIncludeSeverity(severity, minSeverity) {
715
+ return getSeverityLevel(severity) >= getSeverityLevel(minSeverity);
716
+ }
717
+ function getIssueType(type) {
718
+ if (!type) return IssueType.NamingInconsistency;
719
+ const typeMap = {
720
+ "naming-inconsistency": IssueType.NamingInconsistency,
721
+ "naming-quality": IssueType.NamingQuality,
722
+ "pattern-inconsistency": IssueType.PatternInconsistency,
723
+ "architecture-inconsistency": IssueType.ArchitectureInconsistency,
724
+ "error-handling": IssueType.PatternInconsistency,
725
+ "async-style": IssueType.PatternInconsistency,
726
+ "import-style": IssueType.PatternInconsistency,
727
+ "api-design": IssueType.PatternInconsistency
728
+ };
729
+ return typeMap[type] || IssueType.NamingInconsistency;
730
+ }
731
+ function transformToIssue(i) {
732
+ if (i.message && i.location) {
733
+ return {
734
+ type: getIssueType(i.type),
735
+ severity: i.severity,
736
+ message: i.message,
737
+ location: i.location,
738
+ suggestion: i.suggestion
739
+ };
740
+ }
741
+ if (i.identifier || i.type) {
742
+ const line = i.line || 1;
743
+ const column = i.column || 1;
744
+ return {
745
+ type: getIssueType(i.type),
746
+ severity: i.severity,
747
+ message: i.suggestion ? `Naming issue: ${i.suggestion}` : `Naming issue for '${i.identifier || "unknown"}'`,
748
+ location: {
749
+ file: i.file || i.fileName || "",
750
+ line,
751
+ column,
752
+ endLine: line,
753
+ endColumn: column + (i.identifier?.length || 10)
754
+ },
755
+ suggestion: i.suggestion
756
+ };
757
+ }
758
+ if (i.description || i.files) {
759
+ const fileName = Array.isArray(i.files) ? i.files[0] : i.file || "";
760
+ return {
761
+ type: getIssueType(i.type),
762
+ severity: i.severity,
763
+ message: i.description || "Pattern inconsistency found",
764
+ location: {
765
+ file: fileName,
766
+ line: 1,
767
+ column: 1,
768
+ endLine: 1,
769
+ endColumn: 10
770
+ },
771
+ suggestion: i.examples?.[0]
772
+ };
773
+ }
774
+ return {
775
+ type: getIssueType(i.type),
776
+ severity: i.severity,
777
+ message: i.message || "Unknown issue",
778
+ location: i.location || { file: "", line: 1, column: 1 },
779
+ suggestion: i.suggestion
780
+ };
781
+ }
782
+ function calculateConsistencyScore(issues) {
783
+ let totalWeight = 0;
784
+ for (const issue of issues) {
785
+ const val = getSeverityLevel(issue.severity);
786
+ switch (val) {
787
+ case 4:
788
+ totalWeight += 10;
789
+ break;
790
+ case 3:
791
+ totalWeight += 5;
792
+ break;
793
+ case 2:
794
+ totalWeight += 2;
795
+ break;
796
+ case 1:
797
+ totalWeight += 1;
798
+ break;
799
+ default:
800
+ totalWeight += 1;
801
+ }
802
+ }
803
+ return Math.max(0, 1 - totalWeight / 100);
804
+ }
805
+
806
+ export {
807
+ analyzeNamingAST,
808
+ analyzePatterns,
809
+ analyzeConsistency
810
+ };
package/dist/cli.js CHANGED
@@ -579,7 +579,7 @@ async function analyzeNamingGeneralized(files) {
579
579
  if (!spec || spec === "*" || spec === "default") continue;
580
580
  if (exceptions.has(spec)) continue;
581
581
  if (COMMON_ABBREVIATIONS.has(spec.toLowerCase())) continue;
582
- if (!conventions.variablePattern.test(spec) && !conventions.classPattern.test(spec)) {
582
+ if (!conventions.variablePattern.test(spec) && !conventions.classPattern.test(spec) && !conventions.constantPattern.test(spec) && (!conventions.typePattern || !conventions.typePattern.test(spec)) && (!conventions.interfacePattern || !conventions.interfacePattern.test(spec))) {
583
583
  issues.push({
584
584
  type: "naming-inconsistency",
585
585
  identifier: spec,
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  analyzeConsistency
4
- } from "./chunk-TE6JYZD3.mjs";
4
+ } from "./chunk-LSXZH6X6.mjs";
5
5
 
6
6
  // src/cli.ts
7
7
  import { Command } from "commander";
package/dist/index.js CHANGED
@@ -587,7 +587,7 @@ async function analyzeNamingGeneralized(files) {
587
587
  if (!spec || spec === "*" || spec === "default") continue;
588
588
  if (exceptions.has(spec)) continue;
589
589
  if (COMMON_ABBREVIATIONS.has(spec.toLowerCase())) continue;
590
- if (!conventions.variablePattern.test(spec) && !conventions.classPattern.test(spec)) {
590
+ if (!conventions.variablePattern.test(spec) && !conventions.classPattern.test(spec) && !conventions.constantPattern.test(spec) && (!conventions.typePattern || !conventions.typePattern.test(spec)) && (!conventions.interfacePattern || !conventions.interfacePattern.test(spec))) {
591
591
  issues.push({
592
592
  type: "naming-inconsistency",
593
593
  identifier: spec,
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  analyzeConsistency,
3
3
  analyzeNamingAST,
4
4
  analyzePatterns
5
- } from "./chunk-TE6JYZD3.mjs";
5
+ } from "./chunk-LSXZH6X6.mjs";
6
6
 
7
7
  // src/index.ts
8
8
  import { ToolRegistry } from "@aiready/core";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/consistency",
3
- "version": "0.20.11",
3
+ "version": "0.20.13",
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",
@@ -43,7 +43,7 @@
43
43
  "@typescript-eslint/typescript-estree": "^8.53.0",
44
44
  "chalk": "^5.3.0",
45
45
  "commander": "^14.0.0",
46
- "@aiready/core": "0.23.12"
46
+ "@aiready/core": "0.23.14"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/node": "^24.0.0",
@@ -0,0 +1,91 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ snakeCaseToCamelCase,
4
+ detectNamingConventions,
5
+ COMMON_SHORT_WORDS,
6
+ ACCEPTABLE_ABBREVIATIONS,
7
+ } from '../analyzers/naming-constants';
8
+
9
+ describe('snakeCaseToCamelCase', () => {
10
+ it('should convert simple snake_case to camelCase', () => {
11
+ expect(snakeCaseToCamelCase('hello_world')).toBe('helloWorld');
12
+ expect(snakeCaseToCamelCase('foo_bar')).toBe('fooBar');
13
+ });
14
+
15
+ it('should handle single word snake_case', () => {
16
+ expect(snakeCaseToCamelCase('hello')).toBe('hello');
17
+ });
18
+
19
+ it('should handle multiple underscores', () => {
20
+ expect(snakeCaseToCamelCase('a_b_c_d')).toBe('aBCD');
21
+ });
22
+
23
+ it('should handle leading underscores', () => {
24
+ expect(snakeCaseToCamelCase('_private_method')).toBe('privateMethod');
25
+ });
26
+
27
+ it('should handle numbers in snake_case', () => {
28
+ expect(snakeCaseToCamelCase('user_id_1')).toBe('userId1');
29
+ });
30
+
31
+ it('should handle already camelCase strings', () => {
32
+ expect(snakeCaseToCamelCase('alreadyCamelCase')).toBe('alreadyCamelCase');
33
+ });
34
+
35
+ it('should handle uppercase letters after underscore', () => {
36
+ expect(snakeCaseToCamelCase('hello_WORLD')).toBe('helloWORLD');
37
+ });
38
+ });
39
+
40
+ describe('COMMON_SHORT_WORDS', () => {
41
+ it('should include common short English words', () => {
42
+ expect(COMMON_SHORT_WORDS.has('day')).toBe(true);
43
+ expect(COMMON_SHORT_WORDS.has('key')).toBe(true);
44
+ expect(COMMON_SHORT_WORDS.has('net')).toBe(true);
45
+ expect(COMMON_SHORT_WORDS.has('use')).toBe(true);
46
+ });
47
+ });
48
+
49
+ describe('ACCEPTABLE_ABBREVIATIONS', () => {
50
+ it('should include standard identifiers', () => {
51
+ expect(ACCEPTABLE_ABBREVIATIONS.has('id')).toBe(true);
52
+ expect(ACCEPTABLE_ABBREVIATIONS.has('uid')).toBe(true);
53
+ expect(ACCEPTABLE_ABBREVIATIONS.has('pid')).toBe(true);
54
+ });
55
+
56
+ it('should include web/network abbreviations', () => {
57
+ expect(ACCEPTABLE_ABBREVIATIONS.has('url')).toBe(true);
58
+ expect(ACCEPTABLE_ABBREVIATIONS.has('api')).toBe(true);
59
+ expect(ACCEPTABLE_ABBREVIATIONS.has('http')).toBe(true);
60
+ });
61
+
62
+ it('should include data format abbreviations', () => {
63
+ expect(ACCEPTABLE_ABBREVIATIONS.has('json')).toBe(true);
64
+ expect(ACCEPTABLE_ABBREVIATIONS.has('xml')).toBe(true);
65
+ expect(ACCEPTABLE_ABBREVIATIONS.has('yaml')).toBe(true);
66
+ });
67
+
68
+ it('should include boolean helpers', () => {
69
+ expect(ACCEPTABLE_ABBREVIATIONS.has('is')).toBe(true);
70
+ expect(ACCEPTABLE_ABBREVIATIONS.has('has')).toBe(true);
71
+ expect(ACCEPTABLE_ABBREVIATIONS.has('can')).toBe(true);
72
+ });
73
+ });
74
+
75
+ describe('detectNamingConventions', () => {
76
+ it('should detect camelCase as dominant for TypeScript files', () => {
77
+ const result = detectNamingConventions(
78
+ ['file.ts', 'another.ts'],
79
+ [{ type: 'other-issue' }]
80
+ );
81
+ expect(result.dominantConvention).toBe('camelCase');
82
+ expect(result.conventionScore).toBe(0.9);
83
+ });
84
+
85
+ it('should detect mixed conventions when there are many convention issues', () => {
86
+ const manyIssues = Array(40).fill({ type: 'convention-mix' });
87
+ const result = detectNamingConventions(['file.ts'], manyIssues as any);
88
+ expect(result.dominantConvention).toBe('mixed');
89
+ expect(result.conventionScore).toBe(0.5);
90
+ });
91
+ });
@@ -393,7 +393,9 @@ export const ACCEPTABLE_ABBREVIATIONS = new Set([
393
393
  * Convert snake_case to camelCase
394
394
  */
395
395
  export function snakeCaseToCamelCase(str: string): string {
396
- return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
396
+ return str
397
+ .replace(/^_+/, '')
398
+ .replace(/_([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
397
399
  }
398
400
 
399
401
  /**
@@ -124,7 +124,11 @@ export async function analyzeNamingGeneralized(
124
124
 
125
125
  if (
126
126
  !conventions.variablePattern.test(spec) &&
127
- !conventions.classPattern.test(spec)
127
+ !conventions.classPattern.test(spec) &&
128
+ !conventions.constantPattern.test(spec) &&
129
+ (!conventions.typePattern || !conventions.typePattern.test(spec)) &&
130
+ (!conventions.interfacePattern ||
131
+ !conventions.interfacePattern.test(spec))
128
132
  ) {
129
133
  // This is often a 'convention-mix' issue (e.g. importing snake_case into camelCase project)
130
134
  issues.push({