@aiready/consistency 0.16.2 → 0.16.3
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 +10 -10
- package/.turbo/turbo-test.log +12 -10
- package/dist/chunk-AASFXGUR.mjs +1622 -0
- package/dist/chunk-AR7DIZLP.mjs +827 -0
- package/dist/chunk-BMILMNKJ.mjs +1633 -0
- package/dist/chunk-HJCP36VW.mjs +821 -0
- package/dist/chunk-QOIPVP6P.mjs +1607 -0
- package/dist/chunk-RMEQWG52.mjs +1633 -0
- package/dist/chunk-XVW5DKJQ.mjs +1619 -0
- package/dist/cli.js +277 -1019
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +320 -1242
- package/dist/index.mjs +58 -230
- package/package.json +2 -2
- package/src/__tests__/contract.test.ts +18 -2
- package/src/analyzer.ts +49 -28
- package/src/analyzers/naming-ast.ts +188 -328
- package/src/analyzers/naming.ts +52 -365
- package/src/analyzers/patterns.ts +51 -228
- package/src/types.ts +10 -10
- package/src/utils/context-detector.ts +23 -10
package/dist/index.mjs
CHANGED
|
@@ -1,250 +1,78 @@
|
|
|
1
1
|
import {
|
|
2
|
-
ACCEPTABLE_ABBREVIATIONS,
|
|
3
|
-
COMMON_SHORT_WORDS,
|
|
4
2
|
analyzeConsistency,
|
|
5
3
|
analyzeNamingAST,
|
|
6
|
-
analyzePatterns
|
|
7
|
-
|
|
8
|
-
loadNamingConfig,
|
|
9
|
-
snakeCaseToCamelCase
|
|
10
|
-
} from "./chunk-J5IFYDVU.mjs";
|
|
4
|
+
analyzePatterns
|
|
5
|
+
} from "./chunk-HJCP36VW.mjs";
|
|
11
6
|
|
|
12
7
|
// src/analyzers/naming.ts
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const { customAbbreviations, customShortWords, disabledChecks } = await loadNamingConfig(files);
|
|
17
|
-
for (const file of files) {
|
|
18
|
-
const content = await readFileContent(file);
|
|
19
|
-
const fileIssues = analyzeFileNaming(
|
|
20
|
-
file,
|
|
21
|
-
content,
|
|
22
|
-
customAbbreviations,
|
|
23
|
-
customShortWords,
|
|
24
|
-
disabledChecks
|
|
25
|
-
);
|
|
26
|
-
issues.push(...fileIssues);
|
|
27
|
-
}
|
|
28
|
-
return issues;
|
|
29
|
-
}
|
|
30
|
-
function analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks) {
|
|
8
|
+
import { readFileSync } from "fs";
|
|
9
|
+
import { Severity } from "@aiready/core";
|
|
10
|
+
async function analyzeNaming(filePaths) {
|
|
31
11
|
const issues = [];
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const getContextWindow = (index, windowSize = 3) => {
|
|
40
|
-
const start = Math.max(0, index - windowSize);
|
|
41
|
-
const end = Math.min(lines.length, index + windowSize + 1);
|
|
42
|
-
return lines.slice(start, end).join("\n");
|
|
43
|
-
};
|
|
44
|
-
const isShortLivedVariable = (varName, declarationIndex) => {
|
|
45
|
-
const searchRange = 5;
|
|
46
|
-
const endIndex = Math.min(lines.length, declarationIndex + searchRange + 1);
|
|
47
|
-
let usageCount = 0;
|
|
48
|
-
for (let i = declarationIndex; i < endIndex; i++) {
|
|
49
|
-
const regex = new RegExp(`\\b${varName}\\b`, "g");
|
|
50
|
-
const matches = lines[i].match(regex);
|
|
51
|
-
if (matches) {
|
|
52
|
-
usageCount += matches.length;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return usageCount >= 2 && usageCount <= 3;
|
|
56
|
-
};
|
|
57
|
-
lines.forEach((line, index) => {
|
|
58
|
-
const lineNumber = index + 1;
|
|
59
|
-
const contextWindow = getContextWindow(index);
|
|
60
|
-
if (!disabledChecks.has("single-letter")) {
|
|
61
|
-
const singleLetterMatches = line.matchAll(
|
|
62
|
-
/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi
|
|
63
|
-
);
|
|
64
|
-
for (const match of singleLetterMatches) {
|
|
65
|
-
const letter = match[1].toLowerCase();
|
|
66
|
-
const isCoverageContext = /coverage|summary|metrics|pct|percent/i.test(line) || /\.(?:statements|branches|functions|lines)\.pct/i.test(line);
|
|
67
|
-
if (isCoverageContext && ["s", "b", "f", "l"].includes(letter)) {
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
const isInLoopContext = line.includes("for") || /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) || line.includes("=>") || // Arrow function
|
|
71
|
-
/\w+\s*=>\s*/.test(line);
|
|
72
|
-
const isI18nContext = line.includes("useTranslation") || line.includes("i18n.t") || /\bt\s*\(['"]/.test(line);
|
|
73
|
-
const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
|
|
74
|
-
/[a-z]\s*=>/.test(line) || // s => on same line
|
|
75
|
-
// Multi-line arrow function detection: look for pattern in context window
|
|
76
|
-
new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
|
|
77
|
-
new RegExp(
|
|
78
|
-
`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`
|
|
79
|
-
).test(lines[index - 1] || "") && /=>/.test(contextWindow);
|
|
80
|
-
const isShortLived = isShortLivedVariable(letter, index);
|
|
81
|
-
if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !isShortLived && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
|
|
82
|
-
if (isTestFile && ["a", "b", "c", "d", "e", "f", "s"].includes(letter)) {
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
12
|
+
for (const filePath of filePaths) {
|
|
13
|
+
try {
|
|
14
|
+
const content = readFileSync(filePath, "utf-8");
|
|
15
|
+
const lines = content.split("\n");
|
|
16
|
+
lines.forEach((line, index) => {
|
|
17
|
+
const singleLetterMatch = line.match(/\b(const|let|var)\s+([a-hj-km-np-zA-Z])\s*=/);
|
|
18
|
+
if (singleLetterMatch) {
|
|
85
19
|
issues.push({
|
|
86
|
-
file,
|
|
87
|
-
line:
|
|
20
|
+
file: filePath,
|
|
21
|
+
line: index + 1,
|
|
88
22
|
type: "poor-naming",
|
|
89
|
-
identifier:
|
|
90
|
-
severity:
|
|
91
|
-
suggestion:
|
|
23
|
+
identifier: singleLetterMatch[2],
|
|
24
|
+
severity: Severity.Minor,
|
|
25
|
+
suggestion: "Use a more descriptive name than a single letter"
|
|
92
26
|
});
|
|
93
27
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (allAbbreviations.has(abbrev)) {
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
|
|
109
|
-
new RegExp(`\\b${abbrev}\\s*=>`).test(line) || // s => on same line
|
|
110
|
-
// Multi-line arrow function: check context window
|
|
111
|
-
new RegExp(`\\b${abbrev}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
|
|
112
|
-
new RegExp(
|
|
113
|
-
`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`
|
|
114
|
-
).test(lines[index - 1] || "") && new RegExp(`^\\s*${abbrev}\\s*=>`).test(line);
|
|
115
|
-
if (isArrowFunctionParam) {
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
if (abbrev.length <= 2) {
|
|
119
|
-
const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
|
|
120
|
-
if (isDateTimeContext && ["d", "t", "dt"].includes(abbrev)) {
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
const isUserContext = /user|auth|account/i.test(line);
|
|
124
|
-
if (isUserContext && abbrev === "u") {
|
|
125
|
-
continue;
|
|
28
|
+
if (filePath.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
29
|
+
const snakeCaseMatch = line.match(/\b(const|let|var|function)\s+([a-z]+_[a-z0-9_]+)\b/);
|
|
30
|
+
if (snakeCaseMatch) {
|
|
31
|
+
issues.push({
|
|
32
|
+
file: filePath,
|
|
33
|
+
line: index + 1,
|
|
34
|
+
type: "convention-mix",
|
|
35
|
+
identifier: snakeCaseMatch[2],
|
|
36
|
+
severity: Severity.Info,
|
|
37
|
+
suggestion: "Use camelCase instead of snake_case in TypeScript/JavaScript"
|
|
38
|
+
});
|
|
126
39
|
}
|
|
127
40
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
void camelCaseVars;
|
|
143
|
-
const snakeCaseVars = line.match(
|
|
144
|
-
/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/
|
|
145
|
-
);
|
|
146
|
-
if (snakeCaseVars) {
|
|
147
|
-
issues.push({
|
|
148
|
-
file,
|
|
149
|
-
line: lineNumber,
|
|
150
|
-
type: "convention-mix",
|
|
151
|
-
identifier: snakeCaseVars[1],
|
|
152
|
-
severity: "minor",
|
|
153
|
-
suggestion: `Use camelCase '${snakeCaseToCamelCase(snakeCaseVars[1])}' instead of snake_case in TypeScript/JavaScript`
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
if (!disabledChecks.has("unclear")) {
|
|
158
|
-
const booleanMatches = line.matchAll(
|
|
159
|
-
/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi
|
|
160
|
-
);
|
|
161
|
-
for (const match of booleanMatches) {
|
|
162
|
-
const name = match[1];
|
|
163
|
-
if (!name.match(/^(is|has|should|can|will|did)/i)) {
|
|
164
|
-
issues.push({
|
|
165
|
-
file,
|
|
166
|
-
line: lineNumber,
|
|
167
|
-
type: "unclear",
|
|
168
|
-
identifier: name,
|
|
169
|
-
severity: "info",
|
|
170
|
-
suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
if (!disabledChecks.has("unclear")) {
|
|
176
|
-
const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
|
|
177
|
-
for (const match of functionMatches) {
|
|
178
|
-
const name = match[1];
|
|
179
|
-
const isKeyword = [
|
|
180
|
-
"for",
|
|
181
|
-
"if",
|
|
182
|
-
"else",
|
|
183
|
-
"while",
|
|
184
|
-
"do",
|
|
185
|
-
"switch",
|
|
186
|
-
"case",
|
|
187
|
-
"break",
|
|
188
|
-
"continue",
|
|
189
|
-
"return",
|
|
190
|
-
"throw",
|
|
191
|
-
"try",
|
|
192
|
-
"catch",
|
|
193
|
-
"finally",
|
|
194
|
-
"with",
|
|
195
|
-
"yield",
|
|
196
|
-
"await"
|
|
197
|
-
].includes(name);
|
|
198
|
-
if (isKeyword) {
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
const isEntryPoint = ["main", "init", "setup", "bootstrap"].includes(
|
|
202
|
-
name
|
|
203
|
-
);
|
|
204
|
-
if (isEntryPoint) {
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
const isFactoryPattern = name.match(
|
|
208
|
-
/(Factory|Builder|Creator|Generator|Provider|Adapter|Mock)$/
|
|
209
|
-
);
|
|
210
|
-
const isEventHandler = name.match(/^on[A-Z]/);
|
|
211
|
-
const isDescriptiveLong = name.length > 15;
|
|
212
|
-
const isReactHook = name.match(/^use[A-Z]/);
|
|
213
|
-
const isDescriptivePattern = name.match(
|
|
214
|
-
/^(default|total|count|sum|avg|max|min|initial|current|previous|next)\w+/
|
|
215
|
-
) || name.match(
|
|
216
|
-
/\w+(Count|Total|Sum|Average|List|Map|Set|Config|Settings|Options|Props|Data|Info|Details|State|Status|Response|Result)$/
|
|
217
|
-
);
|
|
218
|
-
const isHelperPattern = name.match(/^(to|from|with|without|for|as|into)\w+/) || // toMetadata, withLogger, forPath
|
|
219
|
-
name.match(/^\w+(To|From|With|Without|For|As|Into)\w*$/);
|
|
220
|
-
const isUtilityName = [
|
|
221
|
-
"cn",
|
|
222
|
-
"proxy",
|
|
223
|
-
"sitemap",
|
|
224
|
-
"robots",
|
|
225
|
-
"gtag"
|
|
226
|
-
].includes(name);
|
|
227
|
-
const capitalCount = (name.match(/[A-Z]/g) || []).length;
|
|
228
|
-
const isCompoundWord = capitalCount >= 3;
|
|
229
|
-
const hasActionVerb = name.match(
|
|
230
|
-
/^(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)/
|
|
231
|
-
);
|
|
232
|
-
if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord && !isHelperPattern && !isUtilityName && !isReactHook) {
|
|
233
|
-
issues.push({
|
|
234
|
-
file,
|
|
235
|
-
line: lineNumber,
|
|
236
|
-
type: "unclear",
|
|
237
|
-
identifier: name,
|
|
238
|
-
severity: "info",
|
|
239
|
-
suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
|
|
240
|
-
});
|
|
41
|
+
const shortNameMatch = line.match(/\b(const|let|var)\s+([a-zA-Z0-9]{2,3})\s*=/);
|
|
42
|
+
if (shortNameMatch) {
|
|
43
|
+
const name = shortNameMatch[2].toLowerCase();
|
|
44
|
+
const vagueNames = ["obj", "val", "tmp", "res", "ret", "data"];
|
|
45
|
+
if (vagueNames.includes(name)) {
|
|
46
|
+
issues.push({
|
|
47
|
+
file: filePath,
|
|
48
|
+
line: index + 1,
|
|
49
|
+
type: "poor-naming",
|
|
50
|
+
identifier: name,
|
|
51
|
+
severity: Severity.Minor,
|
|
52
|
+
suggestion: `Avoid vague names like '${name}'`
|
|
53
|
+
});
|
|
54
|
+
}
|
|
241
55
|
}
|
|
242
|
-
}
|
|
56
|
+
});
|
|
57
|
+
} catch (err) {
|
|
58
|
+
void err;
|
|
243
59
|
}
|
|
244
|
-
}
|
|
60
|
+
}
|
|
245
61
|
return issues;
|
|
246
62
|
}
|
|
247
63
|
|
|
64
|
+
// src/analyzers/naming-constants.ts
|
|
65
|
+
function detectNamingConventions(files, allIssues) {
|
|
66
|
+
const camelCaseCount = allIssues.filter(
|
|
67
|
+
(i) => i.type === "convention-mix"
|
|
68
|
+
).length;
|
|
69
|
+
const totalChecks = files.length * 10;
|
|
70
|
+
if (camelCaseCount / totalChecks > 0.3) {
|
|
71
|
+
return { dominantConvention: "mixed", conventionScore: 0.5 };
|
|
72
|
+
}
|
|
73
|
+
return { dominantConvention: "camelCase", conventionScore: 0.9 };
|
|
74
|
+
}
|
|
75
|
+
|
|
248
76
|
// src/scoring.ts
|
|
249
77
|
import { calculateProductivityImpact } from "@aiready/core";
|
|
250
78
|
function calculateConsistencyScore(issues, totalFilesAnalyzed, costConfig) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/consistency",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.3",
|
|
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.19.
|
|
46
|
+
"@aiready/core": "0.19.3"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/node": "^24.0.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
2
|
import { analyzeConsistency } from '../index';
|
|
3
|
-
import { validateSpokeOutput } from '
|
|
3
|
+
import { validateSpokeOutput, SpokeOutputSchema } from '@aiready/core';
|
|
4
4
|
|
|
5
5
|
// Mock core functions
|
|
6
6
|
vi.mock('@aiready/core', async (importOriginal) => {
|
|
@@ -21,14 +21,30 @@ describe('Consistency Spoke Contract Validation', () => {
|
|
|
21
21
|
const fullOutput = {
|
|
22
22
|
results: results.results,
|
|
23
23
|
summary: results.summary,
|
|
24
|
+
metadata: {
|
|
25
|
+
toolName: 'consistency',
|
|
26
|
+
version: '0.1.0',
|
|
27
|
+
timestamp: new Date().toISOString(),
|
|
28
|
+
},
|
|
24
29
|
};
|
|
25
30
|
|
|
31
|
+
// 1. Legacy validation
|
|
26
32
|
const validation = validateSpokeOutput('consistency', fullOutput);
|
|
27
33
|
|
|
28
34
|
if (!validation.valid) {
|
|
29
|
-
console.error('Contract Validation Errors:', validation.errors);
|
|
35
|
+
console.error('Contract Validation Errors (Legacy):', validation.errors);
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
expect(validation.valid).toBe(true);
|
|
39
|
+
|
|
40
|
+
// 2. Zod validation
|
|
41
|
+
const zodResult = SpokeOutputSchema.safeParse(fullOutput);
|
|
42
|
+
if (!zodResult.success) {
|
|
43
|
+
console.error(
|
|
44
|
+
'Contract Validation Errors (Zod):',
|
|
45
|
+
zodResult.error.format()
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
expect(zodResult.success).toBe(true);
|
|
33
49
|
});
|
|
34
50
|
});
|
package/src/analyzer.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { scanFiles } from '@aiready/core';
|
|
1
|
+
import { scanFiles, Severity, IssueType } from '@aiready/core';
|
|
2
2
|
import type { AnalysisResult, Issue } from '@aiready/core';
|
|
3
3
|
import type {
|
|
4
4
|
ConsistencyOptions,
|
|
@@ -20,7 +20,7 @@ export async function analyzeConsistency(
|
|
|
20
20
|
checkNaming = true,
|
|
21
21
|
checkPatterns = true,
|
|
22
22
|
checkArchitecture = false, // Not implemented yet
|
|
23
|
-
minSeverity =
|
|
23
|
+
minSeverity = Severity.Info,
|
|
24
24
|
...scanOptions
|
|
25
25
|
} = options;
|
|
26
26
|
|
|
@@ -59,10 +59,10 @@ export async function analyzeConsistency(
|
|
|
59
59
|
const consistencyIssue: ConsistencyIssue = {
|
|
60
60
|
type:
|
|
61
61
|
issue.type === 'convention-mix'
|
|
62
|
-
?
|
|
63
|
-
:
|
|
62
|
+
? IssueType.NamingInconsistency
|
|
63
|
+
: IssueType.NamingQuality,
|
|
64
64
|
category: 'naming',
|
|
65
|
-
severity: issue.severity,
|
|
65
|
+
severity: getSeverityEnum(issue.severity),
|
|
66
66
|
message: `${issue.type}: ${issue.identifier}`,
|
|
67
67
|
location: {
|
|
68
68
|
file: issue.file,
|
|
@@ -85,9 +85,9 @@ export async function analyzeConsistency(
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
const consistencyIssue: ConsistencyIssue = {
|
|
88
|
-
type:
|
|
88
|
+
type: IssueType.PatternInconsistency,
|
|
89
89
|
category: 'patterns',
|
|
90
|
-
severity: issue.severity,
|
|
90
|
+
severity: getSeverityEnum(issue.severity),
|
|
91
91
|
message: issue.description,
|
|
92
92
|
location: {
|
|
93
93
|
file: issue.files[0] || 'multiple files',
|
|
@@ -120,18 +120,19 @@ export async function analyzeConsistency(
|
|
|
120
120
|
|
|
121
121
|
// Sort results by severity first, then by issue count per file
|
|
122
122
|
results.sort((fileResultA, fileResultB) => {
|
|
123
|
-
const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
|
|
124
|
-
|
|
125
123
|
// Get highest severity in each file
|
|
126
124
|
const maxSeverityA = Math.min(
|
|
127
|
-
...fileResultA.issues.map(
|
|
128
|
-
|
|
129
|
-
|
|
125
|
+
...fileResultA.issues.map((i) => {
|
|
126
|
+
const val = getSeverityLevel((i as ConsistencyIssue).severity);
|
|
127
|
+
// Map 4->0, 3->1, 2->2, 1->3
|
|
128
|
+
return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
|
|
129
|
+
})
|
|
130
130
|
);
|
|
131
131
|
const maxSeverityB = Math.min(
|
|
132
|
-
...fileResultB.issues.map(
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
...fileResultB.issues.map((i) => {
|
|
133
|
+
const val = getSeverityLevel((i as ConsistencyIssue).severity);
|
|
134
|
+
return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
|
|
135
|
+
})
|
|
135
136
|
);
|
|
136
137
|
|
|
137
138
|
// Sort by severity first
|
|
@@ -154,9 +155,6 @@ export async function analyzeConsistency(
|
|
|
154
155
|
shouldIncludeSeverity(i.severity, minSeverity)
|
|
155
156
|
).length;
|
|
156
157
|
|
|
157
|
-
// Detect naming conventions (TODO: re-implement for AST version)
|
|
158
|
-
// const conventionAnalysis = detectNamingConventions(filePaths, namingIssues);
|
|
159
|
-
|
|
160
158
|
return {
|
|
161
159
|
summary: {
|
|
162
160
|
totalIssues: namingCountFiltered + patternCountFiltered,
|
|
@@ -170,21 +168,44 @@ export async function analyzeConsistency(
|
|
|
170
168
|
};
|
|
171
169
|
}
|
|
172
170
|
|
|
171
|
+
function getSeverityLevel(s: any): number {
|
|
172
|
+
if (s === Severity.Critical || s === 'critical') return 4;
|
|
173
|
+
if (s === Severity.Major || s === 'major') return 3;
|
|
174
|
+
if (s === Severity.Minor || s === 'minor') return 2;
|
|
175
|
+
if (s === Severity.Info || s === 'info') return 1;
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function getSeverityEnum(s: any): Severity {
|
|
180
|
+
const val = getSeverityLevel(s);
|
|
181
|
+
switch (val) {
|
|
182
|
+
case 4: return Severity.Critical;
|
|
183
|
+
case 3: return Severity.Major;
|
|
184
|
+
case 2: return Severity.Minor;
|
|
185
|
+
case 1: return Severity.Info;
|
|
186
|
+
default: return Severity.Info;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
173
190
|
function shouldIncludeSeverity(
|
|
174
|
-
severity:
|
|
175
|
-
minSeverity:
|
|
191
|
+
severity: Severity | string,
|
|
192
|
+
minSeverity: Severity | string
|
|
176
193
|
): boolean {
|
|
177
|
-
|
|
178
|
-
return severityLevels[severity] >= severityLevels[minSeverity];
|
|
194
|
+
return getSeverityLevel(severity) >= getSeverityLevel(minSeverity);
|
|
179
195
|
}
|
|
180
196
|
|
|
181
197
|
function calculateConsistencyScore(issues: ConsistencyIssue[]): number {
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
(
|
|
186
|
-
|
|
187
|
-
|
|
198
|
+
let totalWeight = 0;
|
|
199
|
+
for (const issue of issues) {
|
|
200
|
+
const val = getSeverityLevel(issue.severity);
|
|
201
|
+
switch (val) {
|
|
202
|
+
case 4: totalWeight += 10; break;
|
|
203
|
+
case 3: totalWeight += 5; break;
|
|
204
|
+
case 2: totalWeight += 2; break;
|
|
205
|
+
case 1: totalWeight += 1; break;
|
|
206
|
+
default: totalWeight += 1;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
188
209
|
// Score from 0-1, where 1 is perfect
|
|
189
210
|
return Math.max(0, 1 - totalWeight / 100);
|
|
190
211
|
}
|