@aiready/consistency 0.2.0
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 +24 -0
- package/CONTRIBUTING.md +153 -0
- package/LICENSE +21 -0
- package/README.md +170 -0
- package/dist/chunk-BDDMOIU2.mjs +385 -0
- package/dist/chunk-CF4LU7KE.mjs +384 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +626 -0
- package/dist/cli.mjs +224 -0
- package/dist/index.d.mts +77 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +415 -0
- package/dist/index.mjs +12 -0
- package/package.json +63 -0
- package/src/__tests__/analyzer.test.ts +127 -0
- package/src/analyzer.ts +182 -0
- package/src/analyzers/naming.ts +134 -0
- package/src/analyzers/patterns.ts +192 -0
- package/src/cli.ts +254 -0
- package/src/index.ts +11 -0
- package/src/types.ts +62 -0
- package/tsconfig.json +9 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
analyzeConsistency: () => analyzeConsistency,
|
|
24
|
+
analyzeNaming: () => analyzeNaming,
|
|
25
|
+
analyzePatterns: () => analyzePatterns,
|
|
26
|
+
detectNamingConventions: () => detectNamingConventions
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/analyzer.ts
|
|
31
|
+
var import_core3 = require("@aiready/core");
|
|
32
|
+
|
|
33
|
+
// src/analyzers/naming.ts
|
|
34
|
+
var import_core = require("@aiready/core");
|
|
35
|
+
async function analyzeNaming(files) {
|
|
36
|
+
const issues = [];
|
|
37
|
+
for (const file of files) {
|
|
38
|
+
const content = await (0, import_core.readFileContent)(file);
|
|
39
|
+
const fileIssues = analyzeFileNaming(file, content);
|
|
40
|
+
issues.push(...fileIssues);
|
|
41
|
+
}
|
|
42
|
+
return issues;
|
|
43
|
+
}
|
|
44
|
+
function analyzeFileNaming(file, content) {
|
|
45
|
+
const issues = [];
|
|
46
|
+
const lines = content.split("\n");
|
|
47
|
+
lines.forEach((line, index) => {
|
|
48
|
+
const lineNumber = index + 1;
|
|
49
|
+
const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
|
|
50
|
+
for (const match of singleLetterMatches) {
|
|
51
|
+
issues.push({
|
|
52
|
+
file,
|
|
53
|
+
line: lineNumber,
|
|
54
|
+
type: "poor-naming",
|
|
55
|
+
identifier: match[1],
|
|
56
|
+
severity: "minor",
|
|
57
|
+
suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
|
|
61
|
+
for (const match of abbreviationMatches) {
|
|
62
|
+
const abbrev = match[1];
|
|
63
|
+
if (!["id", "url", "api", "db", "fs", "os", "ui"].includes(abbrev.toLowerCase())) {
|
|
64
|
+
issues.push({
|
|
65
|
+
file,
|
|
66
|
+
line: lineNumber,
|
|
67
|
+
type: "abbreviation",
|
|
68
|
+
identifier: abbrev,
|
|
69
|
+
severity: "info",
|
|
70
|
+
suggestion: `Consider using full word instead of abbreviation '${abbrev}'`
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (file.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
75
|
+
const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
|
|
76
|
+
const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
|
|
77
|
+
if (snakeCaseVars) {
|
|
78
|
+
issues.push({
|
|
79
|
+
file,
|
|
80
|
+
line: lineNumber,
|
|
81
|
+
type: "convention-mix",
|
|
82
|
+
identifier: snakeCaseVars[1],
|
|
83
|
+
severity: "minor",
|
|
84
|
+
suggestion: `Use camelCase '${snakeCaseToCamelCase(snakeCaseVars[1])}' instead of snake_case in TypeScript/JavaScript`
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
|
|
89
|
+
for (const match of booleanMatches) {
|
|
90
|
+
const name = match[1];
|
|
91
|
+
if (!name.match(/^(is|has|should|can|will|did)/i)) {
|
|
92
|
+
issues.push({
|
|
93
|
+
file,
|
|
94
|
+
line: lineNumber,
|
|
95
|
+
type: "unclear",
|
|
96
|
+
identifier: name,
|
|
97
|
+
severity: "info",
|
|
98
|
+
suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
|
|
103
|
+
for (const match of functionMatches) {
|
|
104
|
+
const name = match[1];
|
|
105
|
+
if (!name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce)/)) {
|
|
106
|
+
issues.push({
|
|
107
|
+
file,
|
|
108
|
+
line: lineNumber,
|
|
109
|
+
type: "unclear",
|
|
110
|
+
identifier: name,
|
|
111
|
+
severity: "info",
|
|
112
|
+
suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
return issues;
|
|
118
|
+
}
|
|
119
|
+
function snakeCaseToCamelCase(str) {
|
|
120
|
+
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
121
|
+
}
|
|
122
|
+
function detectNamingConventions(files, allIssues) {
|
|
123
|
+
const camelCaseCount = allIssues.filter((i) => i.type === "convention-mix").length;
|
|
124
|
+
const totalChecks = files.length * 10;
|
|
125
|
+
if (camelCaseCount / totalChecks > 0.3) {
|
|
126
|
+
return { dominantConvention: "mixed", conventionScore: 0.5 };
|
|
127
|
+
}
|
|
128
|
+
return { dominantConvention: "camelCase", conventionScore: 0.9 };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/analyzers/patterns.ts
|
|
132
|
+
var import_core2 = require("@aiready/core");
|
|
133
|
+
async function analyzePatterns(files) {
|
|
134
|
+
const issues = [];
|
|
135
|
+
const errorHandlingIssues = await analyzeErrorHandling(files);
|
|
136
|
+
issues.push(...errorHandlingIssues);
|
|
137
|
+
const asyncIssues = await analyzeAsyncPatterns(files);
|
|
138
|
+
issues.push(...asyncIssues);
|
|
139
|
+
const importIssues = await analyzeImportStyles(files);
|
|
140
|
+
issues.push(...importIssues);
|
|
141
|
+
return issues;
|
|
142
|
+
}
|
|
143
|
+
async function analyzeErrorHandling(files) {
|
|
144
|
+
const patterns = {
|
|
145
|
+
tryCatch: [],
|
|
146
|
+
throwsError: [],
|
|
147
|
+
returnsNull: [],
|
|
148
|
+
returnsError: []
|
|
149
|
+
};
|
|
150
|
+
for (const file of files) {
|
|
151
|
+
const content = await (0, import_core2.readFileContent)(file);
|
|
152
|
+
if (content.includes("try {") || content.includes("} catch")) {
|
|
153
|
+
patterns.tryCatch.push(file);
|
|
154
|
+
}
|
|
155
|
+
if (content.match(/throw new \w*Error/)) {
|
|
156
|
+
patterns.throwsError.push(file);
|
|
157
|
+
}
|
|
158
|
+
if (content.match(/return null/)) {
|
|
159
|
+
patterns.returnsNull.push(file);
|
|
160
|
+
}
|
|
161
|
+
if (content.match(/return \{ error:/)) {
|
|
162
|
+
patterns.returnsError.push(file);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const issues = [];
|
|
166
|
+
const strategiesUsed = Object.values(patterns).filter((p) => p.length > 0).length;
|
|
167
|
+
if (strategiesUsed > 2) {
|
|
168
|
+
issues.push({
|
|
169
|
+
files: [.../* @__PURE__ */ new Set([
|
|
170
|
+
...patterns.tryCatch,
|
|
171
|
+
...patterns.throwsError,
|
|
172
|
+
...patterns.returnsNull,
|
|
173
|
+
...patterns.returnsError
|
|
174
|
+
])],
|
|
175
|
+
type: "error-handling",
|
|
176
|
+
description: "Inconsistent error handling strategies across codebase",
|
|
177
|
+
examples: [
|
|
178
|
+
patterns.tryCatch.length > 0 ? `Try-catch used in ${patterns.tryCatch.length} files` : "",
|
|
179
|
+
patterns.throwsError.length > 0 ? `Throws errors in ${patterns.throwsError.length} files` : "",
|
|
180
|
+
patterns.returnsNull.length > 0 ? `Returns null in ${patterns.returnsNull.length} files` : "",
|
|
181
|
+
patterns.returnsError.length > 0 ? `Returns error objects in ${patterns.returnsError.length} files` : ""
|
|
182
|
+
].filter((e) => e),
|
|
183
|
+
severity: "major"
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return issues;
|
|
187
|
+
}
|
|
188
|
+
async function analyzeAsyncPatterns(files) {
|
|
189
|
+
const patterns = {
|
|
190
|
+
asyncAwait: [],
|
|
191
|
+
promises: [],
|
|
192
|
+
callbacks: []
|
|
193
|
+
};
|
|
194
|
+
for (const file of files) {
|
|
195
|
+
const content = await (0, import_core2.readFileContent)(file);
|
|
196
|
+
if (content.match(/async\s+(function|\(|[a-zA-Z])/)) {
|
|
197
|
+
patterns.asyncAwait.push(file);
|
|
198
|
+
}
|
|
199
|
+
if (content.match(/\.then\(/) || content.match(/\.catch\(/)) {
|
|
200
|
+
patterns.promises.push(file);
|
|
201
|
+
}
|
|
202
|
+
if (content.match(/callback\s*\(/) || content.match(/\(\s*err\s*,/)) {
|
|
203
|
+
patterns.callbacks.push(file);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const issues = [];
|
|
207
|
+
if (patterns.callbacks.length > 0 && patterns.asyncAwait.length > 0) {
|
|
208
|
+
issues.push({
|
|
209
|
+
files: [...patterns.callbacks, ...patterns.asyncAwait],
|
|
210
|
+
type: "async-style",
|
|
211
|
+
description: "Mixed async patterns: callbacks and async/await",
|
|
212
|
+
examples: [
|
|
213
|
+
`Callbacks found in: ${patterns.callbacks.slice(0, 3).join(", ")}`,
|
|
214
|
+
`Async/await used in: ${patterns.asyncAwait.slice(0, 3).join(", ")}`
|
|
215
|
+
],
|
|
216
|
+
severity: "minor"
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
if (patterns.promises.length > patterns.asyncAwait.length * 0.3 && patterns.asyncAwait.length > 0) {
|
|
220
|
+
issues.push({
|
|
221
|
+
files: patterns.promises,
|
|
222
|
+
type: "async-style",
|
|
223
|
+
description: "Consider using async/await instead of promise chains for consistency",
|
|
224
|
+
examples: patterns.promises.slice(0, 5),
|
|
225
|
+
severity: "info"
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return issues;
|
|
229
|
+
}
|
|
230
|
+
async function analyzeImportStyles(files) {
|
|
231
|
+
const patterns = {
|
|
232
|
+
esModules: [],
|
|
233
|
+
commonJS: [],
|
|
234
|
+
mixed: []
|
|
235
|
+
};
|
|
236
|
+
for (const file of files) {
|
|
237
|
+
const content = await (0, import_core2.readFileContent)(file);
|
|
238
|
+
const hasESM = content.match(/^import\s+/m);
|
|
239
|
+
const hasCJS = content.match(/require\s*\(/);
|
|
240
|
+
if (hasESM && hasCJS) {
|
|
241
|
+
patterns.mixed.push(file);
|
|
242
|
+
} else if (hasESM) {
|
|
243
|
+
patterns.esModules.push(file);
|
|
244
|
+
} else if (hasCJS) {
|
|
245
|
+
patterns.commonJS.push(file);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const issues = [];
|
|
249
|
+
if (patterns.mixed.length > 0) {
|
|
250
|
+
issues.push({
|
|
251
|
+
files: patterns.mixed,
|
|
252
|
+
type: "import-style",
|
|
253
|
+
description: "Mixed ES modules and CommonJS imports in same files",
|
|
254
|
+
examples: patterns.mixed.slice(0, 5),
|
|
255
|
+
severity: "major"
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
if (patterns.esModules.length > 0 && patterns.commonJS.length > 0) {
|
|
259
|
+
const ratio = patterns.commonJS.length / (patterns.esModules.length + patterns.commonJS.length);
|
|
260
|
+
if (ratio > 0.2 && ratio < 0.8) {
|
|
261
|
+
issues.push({
|
|
262
|
+
files: [...patterns.esModules, ...patterns.commonJS],
|
|
263
|
+
type: "import-style",
|
|
264
|
+
description: "Inconsistent import styles across project",
|
|
265
|
+
examples: [
|
|
266
|
+
`ES modules: ${patterns.esModules.length} files`,
|
|
267
|
+
`CommonJS: ${patterns.commonJS.length} files`
|
|
268
|
+
],
|
|
269
|
+
severity: "minor"
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return issues;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/analyzer.ts
|
|
277
|
+
async function analyzeConsistency(options) {
|
|
278
|
+
const {
|
|
279
|
+
checkNaming = true,
|
|
280
|
+
checkPatterns = true,
|
|
281
|
+
checkArchitecture = false,
|
|
282
|
+
// Not implemented yet
|
|
283
|
+
minSeverity = "info",
|
|
284
|
+
...scanOptions
|
|
285
|
+
} = options;
|
|
286
|
+
const filePaths = await (0, import_core3.scanFiles)(scanOptions);
|
|
287
|
+
const namingIssues = checkNaming ? await analyzeNaming(filePaths) : [];
|
|
288
|
+
const patternIssues = checkPatterns ? await analyzePatterns(filePaths) : [];
|
|
289
|
+
const results = [];
|
|
290
|
+
const fileIssuesMap = /* @__PURE__ */ new Map();
|
|
291
|
+
for (const issue of namingIssues) {
|
|
292
|
+
if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const consistencyIssue = {
|
|
296
|
+
type: issue.type === "convention-mix" ? "naming-inconsistency" : "naming-quality",
|
|
297
|
+
category: "naming",
|
|
298
|
+
severity: issue.severity,
|
|
299
|
+
message: `${issue.type}: ${issue.identifier}`,
|
|
300
|
+
location: {
|
|
301
|
+
file: issue.file,
|
|
302
|
+
line: issue.line,
|
|
303
|
+
column: issue.column
|
|
304
|
+
},
|
|
305
|
+
suggestion: issue.suggestion
|
|
306
|
+
};
|
|
307
|
+
if (!fileIssuesMap.has(issue.file)) {
|
|
308
|
+
fileIssuesMap.set(issue.file, []);
|
|
309
|
+
}
|
|
310
|
+
fileIssuesMap.get(issue.file).push(consistencyIssue);
|
|
311
|
+
}
|
|
312
|
+
for (const issue of patternIssues) {
|
|
313
|
+
if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
const consistencyIssue = {
|
|
317
|
+
type: "pattern-inconsistency",
|
|
318
|
+
category: "patterns",
|
|
319
|
+
severity: issue.severity,
|
|
320
|
+
message: issue.description,
|
|
321
|
+
location: {
|
|
322
|
+
file: issue.files[0] || "multiple files",
|
|
323
|
+
line: 1
|
|
324
|
+
},
|
|
325
|
+
examples: issue.examples,
|
|
326
|
+
suggestion: `Standardize ${issue.type} patterns across ${issue.files.length} files`
|
|
327
|
+
};
|
|
328
|
+
const firstFile = issue.files[0];
|
|
329
|
+
if (firstFile && !fileIssuesMap.has(firstFile)) {
|
|
330
|
+
fileIssuesMap.set(firstFile, []);
|
|
331
|
+
}
|
|
332
|
+
if (firstFile) {
|
|
333
|
+
fileIssuesMap.get(firstFile).push(consistencyIssue);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
for (const [fileName, issues] of fileIssuesMap) {
|
|
337
|
+
results.push({
|
|
338
|
+
fileName,
|
|
339
|
+
issues,
|
|
340
|
+
metrics: {
|
|
341
|
+
consistencyScore: calculateConsistencyScore(issues)
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
const recommendations = generateRecommendations(namingIssues, patternIssues);
|
|
346
|
+
const conventionAnalysis = detectNamingConventions(filePaths, namingIssues);
|
|
347
|
+
return {
|
|
348
|
+
summary: {
|
|
349
|
+
totalIssues: namingIssues.length + patternIssues.length,
|
|
350
|
+
namingIssues: namingIssues.length,
|
|
351
|
+
patternIssues: patternIssues.length,
|
|
352
|
+
architectureIssues: 0,
|
|
353
|
+
filesAnalyzed: filePaths.length
|
|
354
|
+
},
|
|
355
|
+
results,
|
|
356
|
+
recommendations
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function shouldIncludeSeverity(severity, minSeverity) {
|
|
360
|
+
const severityLevels = { info: 0, minor: 1, major: 2, critical: 3 };
|
|
361
|
+
return severityLevels[severity] >= severityLevels[minSeverity];
|
|
362
|
+
}
|
|
363
|
+
function calculateConsistencyScore(issues) {
|
|
364
|
+
const weights = { critical: 10, major: 5, minor: 2, info: 1 };
|
|
365
|
+
const totalWeight = issues.reduce((sum, issue) => sum + weights[issue.severity], 0);
|
|
366
|
+
return Math.max(0, 1 - totalWeight / 100);
|
|
367
|
+
}
|
|
368
|
+
function generateRecommendations(namingIssues, patternIssues) {
|
|
369
|
+
const recommendations = [];
|
|
370
|
+
if (namingIssues.length > 0) {
|
|
371
|
+
const conventionMixCount = namingIssues.filter((i) => i.type === "convention-mix").length;
|
|
372
|
+
if (conventionMixCount > 0) {
|
|
373
|
+
recommendations.push(
|
|
374
|
+
`Standardize naming conventions: Found ${conventionMixCount} snake_case variables in TypeScript/JavaScript (use camelCase)`
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
const poorNamingCount = namingIssues.filter((i) => i.type === "poor-naming").length;
|
|
378
|
+
if (poorNamingCount > 0) {
|
|
379
|
+
recommendations.push(
|
|
380
|
+
`Improve variable naming: Found ${poorNamingCount} single-letter or unclear variable names`
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (patternIssues.length > 0) {
|
|
385
|
+
const errorHandlingIssues = patternIssues.filter((i) => i.type === "error-handling");
|
|
386
|
+
if (errorHandlingIssues.length > 0) {
|
|
387
|
+
recommendations.push(
|
|
388
|
+
"Standardize error handling strategy across the codebase (prefer try-catch with typed errors)"
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
const asyncIssues = patternIssues.filter((i) => i.type === "async-style");
|
|
392
|
+
if (asyncIssues.length > 0) {
|
|
393
|
+
recommendations.push(
|
|
394
|
+
"Use async/await consistently instead of mixing with promise chains or callbacks"
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
const importIssues = patternIssues.filter((i) => i.type === "import-style");
|
|
398
|
+
if (importIssues.length > 0) {
|
|
399
|
+
recommendations.push(
|
|
400
|
+
"Use ES modules consistently across the project (avoid mixing with CommonJS)"
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (recommendations.length === 0) {
|
|
405
|
+
recommendations.push("No major consistency issues found! Your codebase follows good practices.");
|
|
406
|
+
}
|
|
407
|
+
return recommendations;
|
|
408
|
+
}
|
|
409
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
410
|
+
0 && (module.exports = {
|
|
411
|
+
analyzeConsistency,
|
|
412
|
+
analyzeNaming,
|
|
413
|
+
analyzePatterns,
|
|
414
|
+
detectNamingConventions
|
|
415
|
+
});
|
package/dist/index.mjs
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aiready/consistency",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Detects consistency issues in naming, patterns, and architecture that confuse AI models",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"aiready-consistency": "./dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"require": "./dist/index.js",
|
|
15
|
+
"import": "./dist/index.mjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"aiready",
|
|
20
|
+
"consistency",
|
|
21
|
+
"naming-conventions",
|
|
22
|
+
"code-patterns",
|
|
23
|
+
"code-quality",
|
|
24
|
+
"ai-code",
|
|
25
|
+
"tech-debt",
|
|
26
|
+
"refactoring",
|
|
27
|
+
"copilot",
|
|
28
|
+
"chatgpt",
|
|
29
|
+
"claude",
|
|
30
|
+
"ai-assisted-development",
|
|
31
|
+
"architecture",
|
|
32
|
+
"best-practices"
|
|
33
|
+
],
|
|
34
|
+
"author": "AIReady Team",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/caopengau/aiready-consistency.git"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/caopengau/aiready-consistency",
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"chalk": "^5.3.0",
|
|
43
|
+
"commander": "^12.1.0",
|
|
44
|
+
"@aiready/core": "0.5.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^22.10.5",
|
|
48
|
+
"tsup": "^8.3.5",
|
|
49
|
+
"typescript": "^5.7.2",
|
|
50
|
+
"vitest": "^2.1.8"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=18.0.0"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts",
|
|
57
|
+
"dev": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --watch",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"lint": "eslint src",
|
|
60
|
+
"clean": "rm -rf dist",
|
|
61
|
+
"release": "pnpm build && pnpm publish --no-git-checks"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { analyzeConsistency } from '../analyzer';
|
|
3
|
+
import { analyzeNaming } from '../analyzers/naming';
|
|
4
|
+
import { analyzePatterns } from '../analyzers/patterns';
|
|
5
|
+
|
|
6
|
+
describe('analyzeConsistency', () => {
|
|
7
|
+
it('should analyze naming issues', async () => {
|
|
8
|
+
const report = await analyzeConsistency({
|
|
9
|
+
rootDir: './src',
|
|
10
|
+
checkNaming: true,
|
|
11
|
+
checkPatterns: false,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
expect(report).toHaveProperty('summary');
|
|
15
|
+
expect(report).toHaveProperty('results');
|
|
16
|
+
expect(report).toHaveProperty('recommendations');
|
|
17
|
+
expect(report.summary).toHaveProperty('namingIssues');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should detect minimum severity filtering', async () => {
|
|
21
|
+
const report = await analyzeConsistency({
|
|
22
|
+
rootDir: './src',
|
|
23
|
+
minSeverity: 'major',
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// All issues should be major or critical
|
|
27
|
+
for (const result of report.results) {
|
|
28
|
+
for (const issue of result.issues) {
|
|
29
|
+
expect(['major', 'critical']).toContain(issue.severity);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('analyzeNaming', () => {
|
|
36
|
+
it('should detect single letter variables', () => {
|
|
37
|
+
const testCode = `
|
|
38
|
+
const x = 10;
|
|
39
|
+
const y = 20;
|
|
40
|
+
const result = x + y;
|
|
41
|
+
`;
|
|
42
|
+
// In a real test, we'd create temp files or mock file reading
|
|
43
|
+
// For now, this is a placeholder structure
|
|
44
|
+
expect(true).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should detect snake_case in TypeScript files', () => {
|
|
48
|
+
const testCode = `
|
|
49
|
+
const user_name = 'John';
|
|
50
|
+
const user_id = 123;
|
|
51
|
+
`;
|
|
52
|
+
// Test would check for convention-mix issues
|
|
53
|
+
expect(true).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should detect unclear boolean names', () => {
|
|
57
|
+
const testCode = `
|
|
58
|
+
const enabled: boolean = true;
|
|
59
|
+
const active: boolean = false;
|
|
60
|
+
`;
|
|
61
|
+
// Should suggest prefixes like isEnabled, isActive
|
|
62
|
+
expect(true).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should allow common abbreviations', () => {
|
|
66
|
+
const testCode = `
|
|
67
|
+
const id = '123';
|
|
68
|
+
const url = 'https://example.com';
|
|
69
|
+
const api = new ApiClient();
|
|
70
|
+
`;
|
|
71
|
+
// Should not flag these as issues
|
|
72
|
+
expect(true).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('analyzePatterns', () => {
|
|
77
|
+
it('should detect mixed error handling', async () => {
|
|
78
|
+
// Test would analyze files with different error handling approaches
|
|
79
|
+
const issues = await analyzePatterns([]);
|
|
80
|
+
expect(Array.isArray(issues)).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should detect mixed async patterns', async () => {
|
|
84
|
+
// Test would check for async/await vs promises vs callbacks
|
|
85
|
+
const issues = await analyzePatterns([]);
|
|
86
|
+
expect(Array.isArray(issues)).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should detect mixed import styles', async () => {
|
|
90
|
+
// Test would check for ES modules vs CommonJS
|
|
91
|
+
const issues = await analyzePatterns([]);
|
|
92
|
+
expect(Array.isArray(issues)).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('consistency scoring', () => {
|
|
97
|
+
it('should calculate consistency score correctly', () => {
|
|
98
|
+
// Lower issues = higher score
|
|
99
|
+
expect(true).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should weight critical issues more than info', () => {
|
|
103
|
+
// Critical issues should reduce score more
|
|
104
|
+
expect(true).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('recommendations', () => {
|
|
109
|
+
it('should generate relevant recommendations', async () => {
|
|
110
|
+
const report = await analyzeConsistency({
|
|
111
|
+
rootDir: './src',
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(Array.isArray(report.recommendations)).toBe(true);
|
|
115
|
+
expect(report.recommendations.length).toBeGreaterThan(0);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should suggest standardizing error handling', () => {
|
|
119
|
+
// When mixed error handling detected
|
|
120
|
+
expect(true).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should suggest using async/await consistently', () => {
|
|
124
|
+
// When mixed async patterns detected
|
|
125
|
+
expect(true).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
});
|