@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/cli.js
ADDED
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/cli.ts
|
|
27
|
+
var import_commander = require("commander");
|
|
28
|
+
|
|
29
|
+
// src/analyzer.ts
|
|
30
|
+
var import_core3 = require("@aiready/core");
|
|
31
|
+
|
|
32
|
+
// src/analyzers/naming.ts
|
|
33
|
+
var import_core = require("@aiready/core");
|
|
34
|
+
async function analyzeNaming(files) {
|
|
35
|
+
const issues = [];
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
const content = await (0, import_core.readFileContent)(file);
|
|
38
|
+
const fileIssues = analyzeFileNaming(file, content);
|
|
39
|
+
issues.push(...fileIssues);
|
|
40
|
+
}
|
|
41
|
+
return issues;
|
|
42
|
+
}
|
|
43
|
+
function analyzeFileNaming(file, content) {
|
|
44
|
+
const issues = [];
|
|
45
|
+
const lines = content.split("\n");
|
|
46
|
+
lines.forEach((line, index) => {
|
|
47
|
+
const lineNumber = index + 1;
|
|
48
|
+
const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
|
|
49
|
+
for (const match of singleLetterMatches) {
|
|
50
|
+
issues.push({
|
|
51
|
+
file,
|
|
52
|
+
line: lineNumber,
|
|
53
|
+
type: "poor-naming",
|
|
54
|
+
identifier: match[1],
|
|
55
|
+
severity: "minor",
|
|
56
|
+
suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
|
|
60
|
+
for (const match of abbreviationMatches) {
|
|
61
|
+
const abbrev = match[1];
|
|
62
|
+
if (!["id", "url", "api", "db", "fs", "os", "ui"].includes(abbrev.toLowerCase())) {
|
|
63
|
+
issues.push({
|
|
64
|
+
file,
|
|
65
|
+
line: lineNumber,
|
|
66
|
+
type: "abbreviation",
|
|
67
|
+
identifier: abbrev,
|
|
68
|
+
severity: "info",
|
|
69
|
+
suggestion: `Consider using full word instead of abbreviation '${abbrev}'`
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (file.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
74
|
+
const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
|
|
75
|
+
const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
|
|
76
|
+
if (snakeCaseVars) {
|
|
77
|
+
issues.push({
|
|
78
|
+
file,
|
|
79
|
+
line: lineNumber,
|
|
80
|
+
type: "convention-mix",
|
|
81
|
+
identifier: snakeCaseVars[1],
|
|
82
|
+
severity: "minor",
|
|
83
|
+
suggestion: `Use camelCase '${snakeCaseToCamelCase(snakeCaseVars[1])}' instead of snake_case in TypeScript/JavaScript`
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
|
|
88
|
+
for (const match of booleanMatches) {
|
|
89
|
+
const name = match[1];
|
|
90
|
+
if (!name.match(/^(is|has|should|can|will|did)/i)) {
|
|
91
|
+
issues.push({
|
|
92
|
+
file,
|
|
93
|
+
line: lineNumber,
|
|
94
|
+
type: "unclear",
|
|
95
|
+
identifier: name,
|
|
96
|
+
severity: "info",
|
|
97
|
+
suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
|
|
102
|
+
for (const match of functionMatches) {
|
|
103
|
+
const name = match[1];
|
|
104
|
+
if (!name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce)/)) {
|
|
105
|
+
issues.push({
|
|
106
|
+
file,
|
|
107
|
+
line: lineNumber,
|
|
108
|
+
type: "unclear",
|
|
109
|
+
identifier: name,
|
|
110
|
+
severity: "info",
|
|
111
|
+
suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
return issues;
|
|
117
|
+
}
|
|
118
|
+
function snakeCaseToCamelCase(str) {
|
|
119
|
+
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
120
|
+
}
|
|
121
|
+
function detectNamingConventions(files, allIssues) {
|
|
122
|
+
const camelCaseCount = allIssues.filter((i) => i.type === "convention-mix").length;
|
|
123
|
+
const totalChecks = files.length * 10;
|
|
124
|
+
if (camelCaseCount / totalChecks > 0.3) {
|
|
125
|
+
return { dominantConvention: "mixed", conventionScore: 0.5 };
|
|
126
|
+
}
|
|
127
|
+
return { dominantConvention: "camelCase", conventionScore: 0.9 };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/analyzers/patterns.ts
|
|
131
|
+
var import_core2 = require("@aiready/core");
|
|
132
|
+
async function analyzePatterns(files) {
|
|
133
|
+
const issues = [];
|
|
134
|
+
const errorHandlingIssues = await analyzeErrorHandling(files);
|
|
135
|
+
issues.push(...errorHandlingIssues);
|
|
136
|
+
const asyncIssues = await analyzeAsyncPatterns(files);
|
|
137
|
+
issues.push(...asyncIssues);
|
|
138
|
+
const importIssues = await analyzeImportStyles(files);
|
|
139
|
+
issues.push(...importIssues);
|
|
140
|
+
return issues;
|
|
141
|
+
}
|
|
142
|
+
async function analyzeErrorHandling(files) {
|
|
143
|
+
const patterns = {
|
|
144
|
+
tryCatch: [],
|
|
145
|
+
throwsError: [],
|
|
146
|
+
returnsNull: [],
|
|
147
|
+
returnsError: []
|
|
148
|
+
};
|
|
149
|
+
for (const file of files) {
|
|
150
|
+
const content = await (0, import_core2.readFileContent)(file);
|
|
151
|
+
if (content.includes("try {") || content.includes("} catch")) {
|
|
152
|
+
patterns.tryCatch.push(file);
|
|
153
|
+
}
|
|
154
|
+
if (content.match(/throw new \w*Error/)) {
|
|
155
|
+
patterns.throwsError.push(file);
|
|
156
|
+
}
|
|
157
|
+
if (content.match(/return null/)) {
|
|
158
|
+
patterns.returnsNull.push(file);
|
|
159
|
+
}
|
|
160
|
+
if (content.match(/return \{ error:/)) {
|
|
161
|
+
patterns.returnsError.push(file);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const issues = [];
|
|
165
|
+
const strategiesUsed = Object.values(patterns).filter((p) => p.length > 0).length;
|
|
166
|
+
if (strategiesUsed > 2) {
|
|
167
|
+
issues.push({
|
|
168
|
+
files: [.../* @__PURE__ */ new Set([
|
|
169
|
+
...patterns.tryCatch,
|
|
170
|
+
...patterns.throwsError,
|
|
171
|
+
...patterns.returnsNull,
|
|
172
|
+
...patterns.returnsError
|
|
173
|
+
])],
|
|
174
|
+
type: "error-handling",
|
|
175
|
+
description: "Inconsistent error handling strategies across codebase",
|
|
176
|
+
examples: [
|
|
177
|
+
patterns.tryCatch.length > 0 ? `Try-catch used in ${patterns.tryCatch.length} files` : "",
|
|
178
|
+
patterns.throwsError.length > 0 ? `Throws errors in ${patterns.throwsError.length} files` : "",
|
|
179
|
+
patterns.returnsNull.length > 0 ? `Returns null in ${patterns.returnsNull.length} files` : "",
|
|
180
|
+
patterns.returnsError.length > 0 ? `Returns error objects in ${patterns.returnsError.length} files` : ""
|
|
181
|
+
].filter((e) => e),
|
|
182
|
+
severity: "major"
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return issues;
|
|
186
|
+
}
|
|
187
|
+
async function analyzeAsyncPatterns(files) {
|
|
188
|
+
const patterns = {
|
|
189
|
+
asyncAwait: [],
|
|
190
|
+
promises: [],
|
|
191
|
+
callbacks: []
|
|
192
|
+
};
|
|
193
|
+
for (const file of files) {
|
|
194
|
+
const content = await (0, import_core2.readFileContent)(file);
|
|
195
|
+
if (content.match(/async\s+(function|\(|[a-zA-Z])/)) {
|
|
196
|
+
patterns.asyncAwait.push(file);
|
|
197
|
+
}
|
|
198
|
+
if (content.match(/\.then\(/) || content.match(/\.catch\(/)) {
|
|
199
|
+
patterns.promises.push(file);
|
|
200
|
+
}
|
|
201
|
+
if (content.match(/callback\s*\(/) || content.match(/\(\s*err\s*,/)) {
|
|
202
|
+
patterns.callbacks.push(file);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const issues = [];
|
|
206
|
+
if (patterns.callbacks.length > 0 && patterns.asyncAwait.length > 0) {
|
|
207
|
+
issues.push({
|
|
208
|
+
files: [...patterns.callbacks, ...patterns.asyncAwait],
|
|
209
|
+
type: "async-style",
|
|
210
|
+
description: "Mixed async patterns: callbacks and async/await",
|
|
211
|
+
examples: [
|
|
212
|
+
`Callbacks found in: ${patterns.callbacks.slice(0, 3).join(", ")}`,
|
|
213
|
+
`Async/await used in: ${patterns.asyncAwait.slice(0, 3).join(", ")}`
|
|
214
|
+
],
|
|
215
|
+
severity: "minor"
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
if (patterns.promises.length > patterns.asyncAwait.length * 0.3 && patterns.asyncAwait.length > 0) {
|
|
219
|
+
issues.push({
|
|
220
|
+
files: patterns.promises,
|
|
221
|
+
type: "async-style",
|
|
222
|
+
description: "Consider using async/await instead of promise chains for consistency",
|
|
223
|
+
examples: patterns.promises.slice(0, 5),
|
|
224
|
+
severity: "info"
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return issues;
|
|
228
|
+
}
|
|
229
|
+
async function analyzeImportStyles(files) {
|
|
230
|
+
const patterns = {
|
|
231
|
+
esModules: [],
|
|
232
|
+
commonJS: [],
|
|
233
|
+
mixed: []
|
|
234
|
+
};
|
|
235
|
+
for (const file of files) {
|
|
236
|
+
const content = await (0, import_core2.readFileContent)(file);
|
|
237
|
+
const hasESM = content.match(/^import\s+/m);
|
|
238
|
+
const hasCJS = content.match(/require\s*\(/);
|
|
239
|
+
if (hasESM && hasCJS) {
|
|
240
|
+
patterns.mixed.push(file);
|
|
241
|
+
} else if (hasESM) {
|
|
242
|
+
patterns.esModules.push(file);
|
|
243
|
+
} else if (hasCJS) {
|
|
244
|
+
patterns.commonJS.push(file);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const issues = [];
|
|
248
|
+
if (patterns.mixed.length > 0) {
|
|
249
|
+
issues.push({
|
|
250
|
+
files: patterns.mixed,
|
|
251
|
+
type: "import-style",
|
|
252
|
+
description: "Mixed ES modules and CommonJS imports in same files",
|
|
253
|
+
examples: patterns.mixed.slice(0, 5),
|
|
254
|
+
severity: "major"
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
if (patterns.esModules.length > 0 && patterns.commonJS.length > 0) {
|
|
258
|
+
const ratio = patterns.commonJS.length / (patterns.esModules.length + patterns.commonJS.length);
|
|
259
|
+
if (ratio > 0.2 && ratio < 0.8) {
|
|
260
|
+
issues.push({
|
|
261
|
+
files: [...patterns.esModules, ...patterns.commonJS],
|
|
262
|
+
type: "import-style",
|
|
263
|
+
description: "Inconsistent import styles across project",
|
|
264
|
+
examples: [
|
|
265
|
+
`ES modules: ${patterns.esModules.length} files`,
|
|
266
|
+
`CommonJS: ${patterns.commonJS.length} files`
|
|
267
|
+
],
|
|
268
|
+
severity: "minor"
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return issues;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// src/analyzer.ts
|
|
276
|
+
async function analyzeConsistency(options) {
|
|
277
|
+
const {
|
|
278
|
+
checkNaming = true,
|
|
279
|
+
checkPatterns = true,
|
|
280
|
+
checkArchitecture = false,
|
|
281
|
+
// Not implemented yet
|
|
282
|
+
minSeverity = "info",
|
|
283
|
+
...scanOptions
|
|
284
|
+
} = options;
|
|
285
|
+
const filePaths = await (0, import_core3.scanFiles)(scanOptions);
|
|
286
|
+
const namingIssues = checkNaming ? await analyzeNaming(filePaths) : [];
|
|
287
|
+
const patternIssues = checkPatterns ? await analyzePatterns(filePaths) : [];
|
|
288
|
+
const results = [];
|
|
289
|
+
const fileIssuesMap = /* @__PURE__ */ new Map();
|
|
290
|
+
for (const issue of namingIssues) {
|
|
291
|
+
if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const consistencyIssue = {
|
|
295
|
+
type: issue.type === "convention-mix" ? "naming-inconsistency" : "naming-quality",
|
|
296
|
+
category: "naming",
|
|
297
|
+
severity: issue.severity,
|
|
298
|
+
message: `${issue.type}: ${issue.identifier}`,
|
|
299
|
+
location: {
|
|
300
|
+
file: issue.file,
|
|
301
|
+
line: issue.line,
|
|
302
|
+
column: issue.column
|
|
303
|
+
},
|
|
304
|
+
suggestion: issue.suggestion
|
|
305
|
+
};
|
|
306
|
+
if (!fileIssuesMap.has(issue.file)) {
|
|
307
|
+
fileIssuesMap.set(issue.file, []);
|
|
308
|
+
}
|
|
309
|
+
fileIssuesMap.get(issue.file).push(consistencyIssue);
|
|
310
|
+
}
|
|
311
|
+
for (const issue of patternIssues) {
|
|
312
|
+
if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
const consistencyIssue = {
|
|
316
|
+
type: "pattern-inconsistency",
|
|
317
|
+
category: "patterns",
|
|
318
|
+
severity: issue.severity,
|
|
319
|
+
message: issue.description,
|
|
320
|
+
location: {
|
|
321
|
+
file: issue.files[0] || "multiple files",
|
|
322
|
+
line: 1
|
|
323
|
+
},
|
|
324
|
+
examples: issue.examples,
|
|
325
|
+
suggestion: `Standardize ${issue.type} patterns across ${issue.files.length} files`
|
|
326
|
+
};
|
|
327
|
+
const firstFile = issue.files[0];
|
|
328
|
+
if (firstFile && !fileIssuesMap.has(firstFile)) {
|
|
329
|
+
fileIssuesMap.set(firstFile, []);
|
|
330
|
+
}
|
|
331
|
+
if (firstFile) {
|
|
332
|
+
fileIssuesMap.get(firstFile).push(consistencyIssue);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
for (const [fileName, issues] of fileIssuesMap) {
|
|
336
|
+
results.push({
|
|
337
|
+
fileName,
|
|
338
|
+
issues,
|
|
339
|
+
metrics: {
|
|
340
|
+
consistencyScore: calculateConsistencyScore(issues)
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
const recommendations = generateRecommendations(namingIssues, patternIssues);
|
|
345
|
+
const conventionAnalysis = detectNamingConventions(filePaths, namingIssues);
|
|
346
|
+
return {
|
|
347
|
+
summary: {
|
|
348
|
+
totalIssues: namingIssues.length + patternIssues.length,
|
|
349
|
+
namingIssues: namingIssues.length,
|
|
350
|
+
patternIssues: patternIssues.length,
|
|
351
|
+
architectureIssues: 0,
|
|
352
|
+
filesAnalyzed: filePaths.length
|
|
353
|
+
},
|
|
354
|
+
results,
|
|
355
|
+
recommendations
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function shouldIncludeSeverity(severity, minSeverity) {
|
|
359
|
+
const severityLevels = { info: 0, minor: 1, major: 2, critical: 3 };
|
|
360
|
+
return severityLevels[severity] >= severityLevels[minSeverity];
|
|
361
|
+
}
|
|
362
|
+
function calculateConsistencyScore(issues) {
|
|
363
|
+
const weights = { critical: 10, major: 5, minor: 2, info: 1 };
|
|
364
|
+
const totalWeight = issues.reduce((sum, issue) => sum + weights[issue.severity], 0);
|
|
365
|
+
return Math.max(0, 1 - totalWeight / 100);
|
|
366
|
+
}
|
|
367
|
+
function generateRecommendations(namingIssues, patternIssues) {
|
|
368
|
+
const recommendations = [];
|
|
369
|
+
if (namingIssues.length > 0) {
|
|
370
|
+
const conventionMixCount = namingIssues.filter((i) => i.type === "convention-mix").length;
|
|
371
|
+
if (conventionMixCount > 0) {
|
|
372
|
+
recommendations.push(
|
|
373
|
+
`Standardize naming conventions: Found ${conventionMixCount} snake_case variables in TypeScript/JavaScript (use camelCase)`
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
const poorNamingCount = namingIssues.filter((i) => i.type === "poor-naming").length;
|
|
377
|
+
if (poorNamingCount > 0) {
|
|
378
|
+
recommendations.push(
|
|
379
|
+
`Improve variable naming: Found ${poorNamingCount} single-letter or unclear variable names`
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (patternIssues.length > 0) {
|
|
384
|
+
const errorHandlingIssues = patternIssues.filter((i) => i.type === "error-handling");
|
|
385
|
+
if (errorHandlingIssues.length > 0) {
|
|
386
|
+
recommendations.push(
|
|
387
|
+
"Standardize error handling strategy across the codebase (prefer try-catch with typed errors)"
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
const asyncIssues = patternIssues.filter((i) => i.type === "async-style");
|
|
391
|
+
if (asyncIssues.length > 0) {
|
|
392
|
+
recommendations.push(
|
|
393
|
+
"Use async/await consistently instead of mixing with promise chains or callbacks"
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
const importIssues = patternIssues.filter((i) => i.type === "import-style");
|
|
397
|
+
if (importIssues.length > 0) {
|
|
398
|
+
recommendations.push(
|
|
399
|
+
"Use ES modules consistently across the project (avoid mixing with CommonJS)"
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (recommendations.length === 0) {
|
|
404
|
+
recommendations.push("No major consistency issues found! Your codebase follows good practices.");
|
|
405
|
+
}
|
|
406
|
+
return recommendations;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// src/cli.ts
|
|
410
|
+
var import_chalk = __toESM(require("chalk"));
|
|
411
|
+
var import_fs = require("fs");
|
|
412
|
+
var import_core4 = require("@aiready/core");
|
|
413
|
+
var program = new import_commander.Command();
|
|
414
|
+
program.name("aiready-consistency").description("Detect consistency issues in naming, patterns, and architecture").version("0.1.0").addHelpText("after", `
|
|
415
|
+
CONFIGURATION:
|
|
416
|
+
Supports config files: aiready.json, aiready.config.json, .aiready.json, .aireadyrc.json
|
|
417
|
+
CLI options override config file settings
|
|
418
|
+
|
|
419
|
+
ANALYSIS CATEGORIES:
|
|
420
|
+
--naming Check naming conventions and quality (default: enabled)
|
|
421
|
+
--patterns Check code pattern consistency (default: enabled)
|
|
422
|
+
--architecture Check architectural consistency (coming soon)
|
|
423
|
+
|
|
424
|
+
EXAMPLES:
|
|
425
|
+
aiready-consistency . # Full analysis
|
|
426
|
+
aiready-consistency . --no-naming # Skip naming checks
|
|
427
|
+
aiready-consistency . --min-severity major # Only show major+ issues
|
|
428
|
+
aiready-consistency . --output json > report.json # JSON export
|
|
429
|
+
`).argument("<directory>", "Directory to analyze").option("--naming", "Check naming conventions and quality (default: true)").option("--no-naming", "Skip naming analysis").option("--patterns", "Check code pattern consistency (default: true)").option("--no-patterns", "Skip pattern analysis").option("--architecture", "Check architectural consistency (not yet implemented)").option("--min-severity <level>", "Minimum severity: info|minor|major|critical. Default: info").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console|json|markdown", "console").option("--output-file <path>", "Output file path (for json/markdown)").action(async (directory, options) => {
|
|
430
|
+
console.log(import_chalk.default.blue("\u{1F50D} Analyzing consistency...\n"));
|
|
431
|
+
const startTime = Date.now();
|
|
432
|
+
const config = (0, import_core4.loadConfig)(directory);
|
|
433
|
+
const defaults = {
|
|
434
|
+
checkNaming: true,
|
|
435
|
+
checkPatterns: true,
|
|
436
|
+
checkArchitecture: false,
|
|
437
|
+
minSeverity: "info",
|
|
438
|
+
include: void 0,
|
|
439
|
+
exclude: void 0
|
|
440
|
+
};
|
|
441
|
+
const mergedConfig = (0, import_core4.mergeConfigWithDefaults)(config, defaults);
|
|
442
|
+
const finalOptions = {
|
|
443
|
+
rootDir: directory,
|
|
444
|
+
checkNaming: options.naming !== false && mergedConfig.checkNaming,
|
|
445
|
+
checkPatterns: options.patterns !== false && mergedConfig.checkPatterns,
|
|
446
|
+
checkArchitecture: options.architecture || mergedConfig.checkArchitecture,
|
|
447
|
+
minSeverity: options.minSeverity || mergedConfig.minSeverity,
|
|
448
|
+
include: options.include?.split(",") || mergedConfig.include,
|
|
449
|
+
exclude: options.exclude?.split(",") || mergedConfig.exclude
|
|
450
|
+
};
|
|
451
|
+
const report = await analyzeConsistency(finalOptions);
|
|
452
|
+
const elapsedTime = ((Date.now() - startTime) / 1e3).toFixed(2);
|
|
453
|
+
if (options.output === "json") {
|
|
454
|
+
const output = JSON.stringify(report, null, 2);
|
|
455
|
+
if (options.outputFile) {
|
|
456
|
+
(0, import_fs.writeFileSync)(options.outputFile, output);
|
|
457
|
+
console.log(import_chalk.default.green(`\u2713 Report saved to ${options.outputFile}`));
|
|
458
|
+
} else {
|
|
459
|
+
console.log(output);
|
|
460
|
+
}
|
|
461
|
+
} else if (options.output === "markdown") {
|
|
462
|
+
const markdown = generateMarkdownReport(report, elapsedTime);
|
|
463
|
+
if (options.outputFile) {
|
|
464
|
+
(0, import_fs.writeFileSync)(options.outputFile, markdown);
|
|
465
|
+
console.log(import_chalk.default.green(`\u2713 Report saved to ${options.outputFile}`));
|
|
466
|
+
} else {
|
|
467
|
+
console.log(markdown);
|
|
468
|
+
}
|
|
469
|
+
} else {
|
|
470
|
+
displayConsoleReport(report, elapsedTime);
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
program.parse();
|
|
474
|
+
function displayConsoleReport(report, elapsedTime) {
|
|
475
|
+
const { summary, results, recommendations } = report;
|
|
476
|
+
console.log(import_chalk.default.bold("\n\u{1F4CA} Summary\n"));
|
|
477
|
+
console.log(`Files Analyzed: ${import_chalk.default.cyan(summary.filesAnalyzed)}`);
|
|
478
|
+
console.log(`Total Issues: ${import_chalk.default.yellow(summary.totalIssues)}`);
|
|
479
|
+
console.log(` Naming: ${import_chalk.default.yellow(summary.namingIssues)}`);
|
|
480
|
+
console.log(` Patterns: ${import_chalk.default.yellow(summary.patternIssues)}`);
|
|
481
|
+
console.log(` Architecture: ${import_chalk.default.yellow(summary.architectureIssues)}`);
|
|
482
|
+
console.log(`Analysis Time: ${import_chalk.default.gray(elapsedTime + "s")}
|
|
483
|
+
`);
|
|
484
|
+
if (summary.totalIssues === 0) {
|
|
485
|
+
console.log(import_chalk.default.green("\u2728 No consistency issues found! Your codebase is well-maintained.\n"));
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
const namingResults = results.filter(
|
|
489
|
+
(r) => r.issues.some((i) => i.category === "naming")
|
|
490
|
+
);
|
|
491
|
+
const patternResults = results.filter(
|
|
492
|
+
(r) => r.issues.some((i) => i.category === "patterns")
|
|
493
|
+
);
|
|
494
|
+
if (namingResults.length > 0) {
|
|
495
|
+
console.log(import_chalk.default.bold("\u{1F3F7}\uFE0F Naming Issues\n"));
|
|
496
|
+
displayCategoryIssues(namingResults, 5);
|
|
497
|
+
}
|
|
498
|
+
if (patternResults.length > 0) {
|
|
499
|
+
console.log(import_chalk.default.bold("\n\u{1F504} Pattern Issues\n"));
|
|
500
|
+
displayCategoryIssues(patternResults, 5);
|
|
501
|
+
}
|
|
502
|
+
console.log(import_chalk.default.bold("\n\u{1F4A1} Recommendations\n"));
|
|
503
|
+
recommendations.forEach((rec, i) => {
|
|
504
|
+
console.log(`${i + 1}. ${rec}`);
|
|
505
|
+
});
|
|
506
|
+
console.log();
|
|
507
|
+
}
|
|
508
|
+
function displayCategoryIssues(results, maxToShow) {
|
|
509
|
+
let shown = 0;
|
|
510
|
+
for (const result of results) {
|
|
511
|
+
if (shown >= maxToShow) break;
|
|
512
|
+
for (const issue of result.issues) {
|
|
513
|
+
if (shown >= maxToShow) break;
|
|
514
|
+
const severityColor = issue.severity === "critical" ? import_chalk.default.red : issue.severity === "major" ? import_chalk.default.yellow : issue.severity === "minor" ? import_chalk.default.blue : import_chalk.default.gray;
|
|
515
|
+
console.log(
|
|
516
|
+
`${severityColor(issue.severity.toUpperCase())} ${import_chalk.default.dim(
|
|
517
|
+
`${issue.location.file}:${issue.location.line}`
|
|
518
|
+
)}`
|
|
519
|
+
);
|
|
520
|
+
console.log(` ${issue.message}`);
|
|
521
|
+
if (issue.suggestion) {
|
|
522
|
+
console.log(` ${import_chalk.default.dim("\u2192")} ${import_chalk.default.italic(issue.suggestion)}`);
|
|
523
|
+
}
|
|
524
|
+
console.log();
|
|
525
|
+
shown++;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
const remaining = results.reduce((sum, r) => sum + r.issues.length, 0) - shown;
|
|
529
|
+
if (remaining > 0) {
|
|
530
|
+
console.log(import_chalk.default.dim(` ... and ${remaining} more issues
|
|
531
|
+
`));
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function generateMarkdownReport(report, elapsedTime) {
|
|
535
|
+
const { summary, results, recommendations } = report;
|
|
536
|
+
let markdown = `# Consistency Analysis Report
|
|
537
|
+
|
|
538
|
+
`;
|
|
539
|
+
markdown += `**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
540
|
+
`;
|
|
541
|
+
markdown += `**Analysis Time:** ${elapsedTime}s
|
|
542
|
+
|
|
543
|
+
`;
|
|
544
|
+
markdown += `## Summary
|
|
545
|
+
|
|
546
|
+
`;
|
|
547
|
+
markdown += `- **Files Analyzed:** ${summary.filesAnalyzed}
|
|
548
|
+
`;
|
|
549
|
+
markdown += `- **Total Issues:** ${summary.totalIssues}
|
|
550
|
+
`;
|
|
551
|
+
markdown += ` - Naming: ${summary.namingIssues}
|
|
552
|
+
`;
|
|
553
|
+
markdown += ` - Patterns: ${summary.patternIssues}
|
|
554
|
+
`;
|
|
555
|
+
markdown += ` - Architecture: ${summary.architectureIssues}
|
|
556
|
+
|
|
557
|
+
`;
|
|
558
|
+
if (summary.totalIssues === 0) {
|
|
559
|
+
markdown += `\u2728 No consistency issues found!
|
|
560
|
+
`;
|
|
561
|
+
return markdown;
|
|
562
|
+
}
|
|
563
|
+
markdown += `## Issues by Category
|
|
564
|
+
|
|
565
|
+
`;
|
|
566
|
+
const namingResults = results.filter(
|
|
567
|
+
(r) => r.issues.some((i) => i.category === "naming")
|
|
568
|
+
);
|
|
569
|
+
if (namingResults.length > 0) {
|
|
570
|
+
markdown += `### \u{1F3F7}\uFE0F Naming Issues
|
|
571
|
+
|
|
572
|
+
`;
|
|
573
|
+
for (const result of namingResults) {
|
|
574
|
+
for (const issue of result.issues) {
|
|
575
|
+
if (issue.category !== "naming") continue;
|
|
576
|
+
markdown += `- **${issue.severity.toUpperCase()}** \`${issue.location.file}:${issue.location.line}\`
|
|
577
|
+
`;
|
|
578
|
+
markdown += ` - ${issue.message}
|
|
579
|
+
`;
|
|
580
|
+
if (issue.suggestion) {
|
|
581
|
+
markdown += ` - \u{1F4A1} ${issue.suggestion}
|
|
582
|
+
`;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
markdown += `
|
|
587
|
+
`;
|
|
588
|
+
}
|
|
589
|
+
const patternResults = results.filter(
|
|
590
|
+
(r) => r.issues.some((i) => i.category === "patterns")
|
|
591
|
+
);
|
|
592
|
+
if (patternResults.length > 0) {
|
|
593
|
+
markdown += `### \u{1F504} Pattern Issues
|
|
594
|
+
|
|
595
|
+
`;
|
|
596
|
+
for (const result of patternResults) {
|
|
597
|
+
for (const issue of result.issues) {
|
|
598
|
+
if (issue.category !== "patterns") continue;
|
|
599
|
+
markdown += `- **${issue.severity.toUpperCase()}** ${issue.message}
|
|
600
|
+
`;
|
|
601
|
+
if (issue.examples && issue.examples.length > 0) {
|
|
602
|
+
markdown += ` - Examples:
|
|
603
|
+
`;
|
|
604
|
+
issue.examples.forEach((ex) => {
|
|
605
|
+
markdown += ` - ${ex}
|
|
606
|
+
`;
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
if (issue.suggestion) {
|
|
610
|
+
markdown += ` - \u{1F4A1} ${issue.suggestion}
|
|
611
|
+
`;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
markdown += `
|
|
616
|
+
`;
|
|
617
|
+
}
|
|
618
|
+
markdown += `## Recommendations
|
|
619
|
+
|
|
620
|
+
`;
|
|
621
|
+
recommendations.forEach((rec, i) => {
|
|
622
|
+
markdown += `${i + 1}. ${rec}
|
|
623
|
+
`;
|
|
624
|
+
});
|
|
625
|
+
return markdown;
|
|
626
|
+
}
|