@aiready/pattern-detect 0.17.13 → 0.17.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analyzer-entry/index.js +119 -1
- package/dist/analyzer-entry/index.mjs +4 -4
- package/dist/chunk-3CNHAYOD.mjs +499 -0
- package/dist/chunk-4SKGAZEW.mjs +514 -0
- package/dist/chunk-6JNGAY7M.mjs +514 -0
- package/dist/chunk-ATXO4JL7.mjs +404 -0
- package/dist/chunk-C4ZGC4KA.mjs +514 -0
- package/dist/chunk-F42Q2M4O.mjs +143 -0
- package/dist/chunk-G3GZFYRI.mjs +144 -0
- package/dist/chunk-JWP5TCDM.mjs +143 -0
- package/dist/chunk-KDXWIT6W.mjs +408 -0
- package/dist/chunk-KZQXBBR3.mjs +143 -0
- package/dist/chunk-NVV4UFIV.mjs +514 -0
- package/dist/chunk-PFA2DO73.mjs +392 -0
- package/dist/chunk-RH5JPWEC.mjs +143 -0
- package/dist/chunk-UKQFCUQA.mjs +323 -0
- package/dist/chunk-WQX7IHAN.mjs +514 -0
- package/dist/cli.js +123 -5
- package/dist/cli.mjs +8 -8
- package/dist/context-rules-entry/index.js +103 -1
- package/dist/context-rules-entry/index.mjs +1 -1
- package/dist/detector-entry/index.js +103 -1
- package/dist/detector-entry/index.mjs +2 -2
- package/dist/index.js +148 -2
- package/dist/index.mjs +4 -4
- package/dist/scoring-entry/index.js +29 -1
- package/dist/scoring-entry/index.mjs +1 -1
- package/package.json +5 -5
package/dist/cli.mjs
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import {
|
|
3
3
|
analyzePatterns,
|
|
4
4
|
generateSummary
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-WQX7IHAN.mjs";
|
|
6
|
+
import "./chunk-JWP5TCDM.mjs";
|
|
7
7
|
import {
|
|
8
8
|
filterBySeverity
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-KDXWIT6W.mjs";
|
|
10
|
+
import "./chunk-G3GZFYRI.mjs";
|
|
11
11
|
|
|
12
12
|
// src/cli.ts
|
|
13
13
|
import { Command } from "commander";
|
|
@@ -157,8 +157,8 @@ function generateHTMLReport(results, summary) {
|
|
|
157
157
|
{
|
|
158
158
|
title: "Pattern Detection Report",
|
|
159
159
|
packageName: "pattern-detect",
|
|
160
|
-
packageUrl: "https://github.com/
|
|
161
|
-
bugUrl: "https://github.com/
|
|
160
|
+
packageUrl: "https://github.com/getaiready/aiready-pattern-detect",
|
|
161
|
+
bugUrl: "https://github.com/getaiready/aiready-pattern-detect/issues",
|
|
162
162
|
version: metadata.version,
|
|
163
163
|
emoji: "\u{1F50D}"
|
|
164
164
|
},
|
|
@@ -509,12 +509,12 @@ function printGuidance() {
|
|
|
509
509
|
function printFooter() {
|
|
510
510
|
console.log(
|
|
511
511
|
chalk2.dim(
|
|
512
|
-
"\n\u2B50 Like AIReady? Star us on GitHub: https://github.com/
|
|
512
|
+
"\n\u2B50 Like AIReady? Star us on GitHub: https://github.com/getaiready/aiready-pattern-detect"
|
|
513
513
|
)
|
|
514
514
|
);
|
|
515
515
|
console.log(
|
|
516
516
|
chalk2.dim(
|
|
517
|
-
"\u{1F41B} Found a bug? Report it: https://github.com/
|
|
517
|
+
"\u{1F41B} Found a bug? Report it: https://github.com/getaiready/aiready-pattern-detect/issues\n"
|
|
518
518
|
)
|
|
519
519
|
);
|
|
520
520
|
}
|
|
@@ -151,19 +151,91 @@ var INFRA_RULES = [
|
|
|
151
151
|
{
|
|
152
152
|
name: "cli-command-definitions",
|
|
153
153
|
detect: (file, code) => {
|
|
154
|
-
const
|
|
154
|
+
const basename = file.split("/").pop() || "";
|
|
155
|
+
const isCliFile = file.includes("/commands/") || file.includes("/cli/") || file.endsWith(".command.ts") || basename === "cli.ts" || basename === "cli.js" || basename === "cli.tsx" || basename === "cli-action.ts";
|
|
155
156
|
const hasCommandPattern = (code.includes(".command(") || code.includes("defineCommand")) && (code.includes(".description(") || code.includes(".option(")) && (code.includes(".action(") || code.includes("async"));
|
|
156
157
|
return isCliFile && hasCommandPattern;
|
|
157
158
|
},
|
|
158
159
|
severity: import_core3.Severity.Info,
|
|
159
160
|
reason: "CLI command definitions follow standard Commander.js patterns and are intentionally similar",
|
|
160
161
|
suggestion: "Command boilerplate duplication is acceptable for CLI interfaces"
|
|
162
|
+
},
|
|
163
|
+
// DynamoDB Single-Table Design - Standard single-table patterns with prefixed keys
|
|
164
|
+
{
|
|
165
|
+
name: "dynamodb-single-table",
|
|
166
|
+
detect: (file, code) => {
|
|
167
|
+
const hasDynamoDBPattern = code.includes("docClient") || code.includes("dynamodb") || code.includes("DynamoDB") || code.includes("queryItems") || code.includes("putItem") || code.includes("getItem") || code.includes("updateItem") || code.includes("deleteItem");
|
|
168
|
+
const hasKeyPrefix = code.includes("userId:") && code.includes("#") || code.includes("pk:") && code.includes("#") || code.includes("Key:") && code.includes("#") || /[A-Z]+#/.test(code);
|
|
169
|
+
const hasSingleTablePattern = code.includes("KeyConditionExpression") || code.includes("pk =") || code.includes("sk =") || code.includes("userId") && code.includes("timestamp");
|
|
170
|
+
return hasDynamoDBPattern && (hasKeyPrefix || hasSingleTablePattern);
|
|
171
|
+
},
|
|
172
|
+
severity: import_core3.Severity.Info,
|
|
173
|
+
reason: "DynamoDB single-table design with prefixed keys is a standard pattern for efficient data access",
|
|
174
|
+
suggestion: "Single-table query patterns are intentionally similar and should not be refactored"
|
|
175
|
+
},
|
|
176
|
+
// CLI Main Function Boilerplate - Standard argument parsing patterns
|
|
177
|
+
{
|
|
178
|
+
name: "cli-main-boilerplate",
|
|
179
|
+
detect: (file, code) => {
|
|
180
|
+
const basename = file.split("/").pop() || "";
|
|
181
|
+
const isCliFile = file.includes("/cli/") || file.includes("/commands/") || basename.startsWith("cli") || basename.endsWith(".cli.ts") || basename.endsWith(".cli.js");
|
|
182
|
+
const hasMainFunction = code.includes("function main()") || code.includes("async function main()") || code.includes("const main =") || code.includes("main()");
|
|
183
|
+
const hasArgParsing = code.includes("process.argv") || code.includes("yargs") || code.includes("commander") || code.includes("minimist") || code.includes(".parse(") || code.includes("args") && code.includes("._");
|
|
184
|
+
return isCliFile && hasMainFunction && hasArgParsing;
|
|
185
|
+
},
|
|
186
|
+
severity: import_core3.Severity.Info,
|
|
187
|
+
reason: "CLI main functions with argument parsing follow standard boilerplate patterns",
|
|
188
|
+
suggestion: "CLI argument parsing boilerplate is acceptable and should not be flagged as duplication"
|
|
161
189
|
}
|
|
162
190
|
];
|
|
163
191
|
|
|
164
192
|
// src/rules/categories/logic-rules.ts
|
|
165
193
|
var import_core4 = require("@aiready/core");
|
|
166
194
|
var LOGIC_RULES = [
|
|
195
|
+
// Enum Semantic Difference - Different enum names indicate different semantic meanings
|
|
196
|
+
{
|
|
197
|
+
name: "enum-semantic-difference",
|
|
198
|
+
detect: (file, code) => {
|
|
199
|
+
const enumRegex = /(?:export\s+)?(?:const\s+)?enum\s+([A-Z][a-zA-Z0-9]*)/g;
|
|
200
|
+
const enums = [];
|
|
201
|
+
let match;
|
|
202
|
+
while ((match = enumRegex.exec(code)) !== null) {
|
|
203
|
+
enums.push(match[1]);
|
|
204
|
+
}
|
|
205
|
+
return enums.length > 0;
|
|
206
|
+
},
|
|
207
|
+
severity: import_core4.Severity.Info,
|
|
208
|
+
reason: "Enums with different names represent different semantic domain concepts, even if they share similar values",
|
|
209
|
+
suggestion: "Different enums (e.g., EscalationPriority vs HealthSeverity) serve different purposes and should not be merged"
|
|
210
|
+
},
|
|
211
|
+
// Enum Value Similarity - Common enum values like LOW, MEDIUM, HIGH are standard
|
|
212
|
+
{
|
|
213
|
+
name: "enum-value-similarity",
|
|
214
|
+
detect: (file, code) => {
|
|
215
|
+
const hasCommonEnumValues = (code.includes("LOW = 'low'") || code.includes("LOW = 0") || code.includes("LOW = 'LOW'")) && (code.includes("HIGH = 'high'") || code.includes("HIGH = 2") || code.includes("HIGH = 'HIGH'")) && (code.includes("MEDIUM = 'medium'") || code.includes("MEDIUM = 1") || code.includes("MEDIUM = 'MEDIUM'"));
|
|
216
|
+
const isEnumDefinition = /(?:export\s+)?(?:const\s+)?enum\s+/.test(code) || code.includes("enum ") && code.includes("{") && code.includes("}");
|
|
217
|
+
return hasCommonEnumValues && isEnumDefinition;
|
|
218
|
+
},
|
|
219
|
+
severity: import_core4.Severity.Info,
|
|
220
|
+
reason: "Common enum values (LOW, MEDIUM, HIGH, CRITICAL) are standard patterns used across different domain enums",
|
|
221
|
+
suggestion: "Enum value similarity is expected for severity/priority enums and should not be flagged as duplication"
|
|
222
|
+
},
|
|
223
|
+
// Re-export / Barrel files - Intentional API surface consolidation
|
|
224
|
+
{
|
|
225
|
+
name: "re-export-files",
|
|
226
|
+
detect: (file, code) => {
|
|
227
|
+
const isIndexFile = file.endsWith("/index.ts") || file.endsWith("/index.js") || file.endsWith("/index.tsx") || file.endsWith("/index.jsx");
|
|
228
|
+
const lines = code.split("\n").filter((l) => l.trim());
|
|
229
|
+
if (lines.length === 0) return false;
|
|
230
|
+
const reExportLines = lines.filter(
|
|
231
|
+
(l) => /^export\s+(\{[^}]+\}|\*)\s+from\s+/.test(l.trim()) || /^export\s+\*\s+as\s+\w+\s+from\s+/.test(l.trim())
|
|
232
|
+
).length;
|
|
233
|
+
return isIndexFile && reExportLines > 0 && reExportLines / lines.length > 0.5;
|
|
234
|
+
},
|
|
235
|
+
severity: import_core4.Severity.Info,
|
|
236
|
+
reason: "Barrel/index files intentionally re-export for API surface consolidation",
|
|
237
|
+
suggestion: "Re-exports in barrel files are expected and not true duplication"
|
|
238
|
+
},
|
|
167
239
|
// Type Definitions - Duplication for type safety and module independence
|
|
168
240
|
{
|
|
169
241
|
name: "type-definitions",
|
|
@@ -177,6 +249,20 @@ var LOGIC_RULES = [
|
|
|
177
249
|
reason: "Type/interface definitions are intentionally duplicated for module independence",
|
|
178
250
|
suggestion: "Extract to shared types package only if causing maintenance burden"
|
|
179
251
|
},
|
|
252
|
+
// Cross-Package Type Definitions - Different packages may have similar types
|
|
253
|
+
{
|
|
254
|
+
name: "cross-package-types",
|
|
255
|
+
detect: (file, code) => {
|
|
256
|
+
const hasTypeDefinition = code.includes("interface ") || code.includes("type ") || code.includes("enum ");
|
|
257
|
+
const isPackageOrApp = file.includes("/packages/") || file.includes("/apps/") || file.includes("/core/");
|
|
258
|
+
const packageMatch = file.match(/\/(packages|apps|core)\/([^/]+)\//);
|
|
259
|
+
const hasPackageStructure = packageMatch !== null;
|
|
260
|
+
return hasTypeDefinition && isPackageOrApp && hasPackageStructure;
|
|
261
|
+
},
|
|
262
|
+
severity: import_core4.Severity.Info,
|
|
263
|
+
reason: "Types in different packages/modules are often intentionally similar for module independence",
|
|
264
|
+
suggestion: "Cross-package type duplication is acceptable for decoupled module architecture"
|
|
265
|
+
},
|
|
180
266
|
// Utility Functions - Small helpers in dedicated utility files
|
|
181
267
|
{
|
|
182
268
|
name: "utility-functions",
|
|
@@ -259,6 +345,22 @@ var LOGIC_RULES = [
|
|
|
259
345
|
severity: import_core4.Severity.Info,
|
|
260
346
|
reason: "Validation functions are inherently similar and often intentionally duplicated for domain clarity",
|
|
261
347
|
suggestion: "Consider extracting to shared validators only if validation logic becomes complex"
|
|
348
|
+
},
|
|
349
|
+
// Singleton Getter Pattern - Standard singleton initialization pattern
|
|
350
|
+
{
|
|
351
|
+
name: "singleton-getter",
|
|
352
|
+
detect: (file, code) => {
|
|
353
|
+
const hasSingletonGetter = /(?:export\s+)?(?:async\s+)?function\s+get[A-Z][a-zA-Z0-9]*\s*\(/.test(
|
|
354
|
+
code
|
|
355
|
+
) || /(?:export\s+)?const\s+get[A-Z][a-zA-Z0-9]*\s*=\s*(?:async\s+)?\(\)\s*=>/.test(
|
|
356
|
+
code
|
|
357
|
+
);
|
|
358
|
+
const hasSingletonPattern = code.includes("if (!") && code.includes("instance") && code.includes(" = ") || code.includes("if (!_") && code.includes(" = new ") || code.includes("if (") && code.includes(" === null") && code.includes(" = new ");
|
|
359
|
+
return hasSingletonGetter && hasSingletonPattern;
|
|
360
|
+
},
|
|
361
|
+
severity: import_core4.Severity.Info,
|
|
362
|
+
reason: "Singleton getter functions follow standard initialization pattern and are intentionally similar",
|
|
363
|
+
suggestion: "Singleton getters are boilerplate and acceptable duplication for lazy initialization"
|
|
262
364
|
}
|
|
263
365
|
];
|
|
264
366
|
|
|
@@ -151,19 +151,91 @@ var INFRA_RULES = [
|
|
|
151
151
|
{
|
|
152
152
|
name: "cli-command-definitions",
|
|
153
153
|
detect: (file, code) => {
|
|
154
|
-
const
|
|
154
|
+
const basename = file.split("/").pop() || "";
|
|
155
|
+
const isCliFile = file.includes("/commands/") || file.includes("/cli/") || file.endsWith(".command.ts") || basename === "cli.ts" || basename === "cli.js" || basename === "cli.tsx" || basename === "cli-action.ts";
|
|
155
156
|
const hasCommandPattern = (code.includes(".command(") || code.includes("defineCommand")) && (code.includes(".description(") || code.includes(".option(")) && (code.includes(".action(") || code.includes("async"));
|
|
156
157
|
return isCliFile && hasCommandPattern;
|
|
157
158
|
},
|
|
158
159
|
severity: import_core3.Severity.Info,
|
|
159
160
|
reason: "CLI command definitions follow standard Commander.js patterns and are intentionally similar",
|
|
160
161
|
suggestion: "Command boilerplate duplication is acceptable for CLI interfaces"
|
|
162
|
+
},
|
|
163
|
+
// DynamoDB Single-Table Design - Standard single-table patterns with prefixed keys
|
|
164
|
+
{
|
|
165
|
+
name: "dynamodb-single-table",
|
|
166
|
+
detect: (file, code) => {
|
|
167
|
+
const hasDynamoDBPattern = code.includes("docClient") || code.includes("dynamodb") || code.includes("DynamoDB") || code.includes("queryItems") || code.includes("putItem") || code.includes("getItem") || code.includes("updateItem") || code.includes("deleteItem");
|
|
168
|
+
const hasKeyPrefix = code.includes("userId:") && code.includes("#") || code.includes("pk:") && code.includes("#") || code.includes("Key:") && code.includes("#") || /[A-Z]+#/.test(code);
|
|
169
|
+
const hasSingleTablePattern = code.includes("KeyConditionExpression") || code.includes("pk =") || code.includes("sk =") || code.includes("userId") && code.includes("timestamp");
|
|
170
|
+
return hasDynamoDBPattern && (hasKeyPrefix || hasSingleTablePattern);
|
|
171
|
+
},
|
|
172
|
+
severity: import_core3.Severity.Info,
|
|
173
|
+
reason: "DynamoDB single-table design with prefixed keys is a standard pattern for efficient data access",
|
|
174
|
+
suggestion: "Single-table query patterns are intentionally similar and should not be refactored"
|
|
175
|
+
},
|
|
176
|
+
// CLI Main Function Boilerplate - Standard argument parsing patterns
|
|
177
|
+
{
|
|
178
|
+
name: "cli-main-boilerplate",
|
|
179
|
+
detect: (file, code) => {
|
|
180
|
+
const basename = file.split("/").pop() || "";
|
|
181
|
+
const isCliFile = file.includes("/cli/") || file.includes("/commands/") || basename.startsWith("cli") || basename.endsWith(".cli.ts") || basename.endsWith(".cli.js");
|
|
182
|
+
const hasMainFunction = code.includes("function main()") || code.includes("async function main()") || code.includes("const main =") || code.includes("main()");
|
|
183
|
+
const hasArgParsing = code.includes("process.argv") || code.includes("yargs") || code.includes("commander") || code.includes("minimist") || code.includes(".parse(") || code.includes("args") && code.includes("._");
|
|
184
|
+
return isCliFile && hasMainFunction && hasArgParsing;
|
|
185
|
+
},
|
|
186
|
+
severity: import_core3.Severity.Info,
|
|
187
|
+
reason: "CLI main functions with argument parsing follow standard boilerplate patterns",
|
|
188
|
+
suggestion: "CLI argument parsing boilerplate is acceptable and should not be flagged as duplication"
|
|
161
189
|
}
|
|
162
190
|
];
|
|
163
191
|
|
|
164
192
|
// src/rules/categories/logic-rules.ts
|
|
165
193
|
var import_core4 = require("@aiready/core");
|
|
166
194
|
var LOGIC_RULES = [
|
|
195
|
+
// Enum Semantic Difference - Different enum names indicate different semantic meanings
|
|
196
|
+
{
|
|
197
|
+
name: "enum-semantic-difference",
|
|
198
|
+
detect: (file, code) => {
|
|
199
|
+
const enumRegex = /(?:export\s+)?(?:const\s+)?enum\s+([A-Z][a-zA-Z0-9]*)/g;
|
|
200
|
+
const enums = [];
|
|
201
|
+
let match;
|
|
202
|
+
while ((match = enumRegex.exec(code)) !== null) {
|
|
203
|
+
enums.push(match[1]);
|
|
204
|
+
}
|
|
205
|
+
return enums.length > 0;
|
|
206
|
+
},
|
|
207
|
+
severity: import_core4.Severity.Info,
|
|
208
|
+
reason: "Enums with different names represent different semantic domain concepts, even if they share similar values",
|
|
209
|
+
suggestion: "Different enums (e.g., EscalationPriority vs HealthSeverity) serve different purposes and should not be merged"
|
|
210
|
+
},
|
|
211
|
+
// Enum Value Similarity - Common enum values like LOW, MEDIUM, HIGH are standard
|
|
212
|
+
{
|
|
213
|
+
name: "enum-value-similarity",
|
|
214
|
+
detect: (file, code) => {
|
|
215
|
+
const hasCommonEnumValues = (code.includes("LOW = 'low'") || code.includes("LOW = 0") || code.includes("LOW = 'LOW'")) && (code.includes("HIGH = 'high'") || code.includes("HIGH = 2") || code.includes("HIGH = 'HIGH'")) && (code.includes("MEDIUM = 'medium'") || code.includes("MEDIUM = 1") || code.includes("MEDIUM = 'MEDIUM'"));
|
|
216
|
+
const isEnumDefinition = /(?:export\s+)?(?:const\s+)?enum\s+/.test(code) || code.includes("enum ") && code.includes("{") && code.includes("}");
|
|
217
|
+
return hasCommonEnumValues && isEnumDefinition;
|
|
218
|
+
},
|
|
219
|
+
severity: import_core4.Severity.Info,
|
|
220
|
+
reason: "Common enum values (LOW, MEDIUM, HIGH, CRITICAL) are standard patterns used across different domain enums",
|
|
221
|
+
suggestion: "Enum value similarity is expected for severity/priority enums and should not be flagged as duplication"
|
|
222
|
+
},
|
|
223
|
+
// Re-export / Barrel files - Intentional API surface consolidation
|
|
224
|
+
{
|
|
225
|
+
name: "re-export-files",
|
|
226
|
+
detect: (file, code) => {
|
|
227
|
+
const isIndexFile = file.endsWith("/index.ts") || file.endsWith("/index.js") || file.endsWith("/index.tsx") || file.endsWith("/index.jsx");
|
|
228
|
+
const lines = code.split("\n").filter((l) => l.trim());
|
|
229
|
+
if (lines.length === 0) return false;
|
|
230
|
+
const reExportLines = lines.filter(
|
|
231
|
+
(l) => /^export\s+(\{[^}]+\}|\*)\s+from\s+/.test(l.trim()) || /^export\s+\*\s+as\s+\w+\s+from\s+/.test(l.trim())
|
|
232
|
+
).length;
|
|
233
|
+
return isIndexFile && reExportLines > 0 && reExportLines / lines.length > 0.5;
|
|
234
|
+
},
|
|
235
|
+
severity: import_core4.Severity.Info,
|
|
236
|
+
reason: "Barrel/index files intentionally re-export for API surface consolidation",
|
|
237
|
+
suggestion: "Re-exports in barrel files are expected and not true duplication"
|
|
238
|
+
},
|
|
167
239
|
// Type Definitions - Duplication for type safety and module independence
|
|
168
240
|
{
|
|
169
241
|
name: "type-definitions",
|
|
@@ -177,6 +249,20 @@ var LOGIC_RULES = [
|
|
|
177
249
|
reason: "Type/interface definitions are intentionally duplicated for module independence",
|
|
178
250
|
suggestion: "Extract to shared types package only if causing maintenance burden"
|
|
179
251
|
},
|
|
252
|
+
// Cross-Package Type Definitions - Different packages may have similar types
|
|
253
|
+
{
|
|
254
|
+
name: "cross-package-types",
|
|
255
|
+
detect: (file, code) => {
|
|
256
|
+
const hasTypeDefinition = code.includes("interface ") || code.includes("type ") || code.includes("enum ");
|
|
257
|
+
const isPackageOrApp = file.includes("/packages/") || file.includes("/apps/") || file.includes("/core/");
|
|
258
|
+
const packageMatch = file.match(/\/(packages|apps|core)\/([^/]+)\//);
|
|
259
|
+
const hasPackageStructure = packageMatch !== null;
|
|
260
|
+
return hasTypeDefinition && isPackageOrApp && hasPackageStructure;
|
|
261
|
+
},
|
|
262
|
+
severity: import_core4.Severity.Info,
|
|
263
|
+
reason: "Types in different packages/modules are often intentionally similar for module independence",
|
|
264
|
+
suggestion: "Cross-package type duplication is acceptable for decoupled module architecture"
|
|
265
|
+
},
|
|
180
266
|
// Utility Functions - Small helpers in dedicated utility files
|
|
181
267
|
{
|
|
182
268
|
name: "utility-functions",
|
|
@@ -259,6 +345,22 @@ var LOGIC_RULES = [
|
|
|
259
345
|
severity: import_core4.Severity.Info,
|
|
260
346
|
reason: "Validation functions are inherently similar and often intentionally duplicated for domain clarity",
|
|
261
347
|
suggestion: "Consider extracting to shared validators only if validation logic becomes complex"
|
|
348
|
+
},
|
|
349
|
+
// Singleton Getter Pattern - Standard singleton initialization pattern
|
|
350
|
+
{
|
|
351
|
+
name: "singleton-getter",
|
|
352
|
+
detect: (file, code) => {
|
|
353
|
+
const hasSingletonGetter = /(?:export\s+)?(?:async\s+)?function\s+get[A-Z][a-zA-Z0-9]*\s*\(/.test(
|
|
354
|
+
code
|
|
355
|
+
) || /(?:export\s+)?const\s+get[A-Z][a-zA-Z0-9]*\s*=\s*(?:async\s+)?\(\)\s*=>/.test(
|
|
356
|
+
code
|
|
357
|
+
);
|
|
358
|
+
const hasSingletonPattern = code.includes("if (!") && code.includes("instance") && code.includes(" = ") || code.includes("if (!_") && code.includes(" = new ") || code.includes("if (") && code.includes(" === null") && code.includes(" = new ");
|
|
359
|
+
return hasSingletonGetter && hasSingletonPattern;
|
|
360
|
+
},
|
|
361
|
+
severity: import_core4.Severity.Info,
|
|
362
|
+
reason: "Singleton getter functions follow standard initialization pattern and are intentionally similar",
|
|
363
|
+
suggestion: "Singleton getters are boilerplate and acceptable duplication for lazy initialization"
|
|
262
364
|
}
|
|
263
365
|
];
|
|
264
366
|
|
package/dist/index.js
CHANGED
|
@@ -183,19 +183,91 @@ var INFRA_RULES = [
|
|
|
183
183
|
{
|
|
184
184
|
name: "cli-command-definitions",
|
|
185
185
|
detect: (file, code) => {
|
|
186
|
-
const
|
|
186
|
+
const basename = file.split("/").pop() || "";
|
|
187
|
+
const isCliFile = file.includes("/commands/") || file.includes("/cli/") || file.endsWith(".command.ts") || basename === "cli.ts" || basename === "cli.js" || basename === "cli.tsx" || basename === "cli-action.ts";
|
|
187
188
|
const hasCommandPattern = (code.includes(".command(") || code.includes("defineCommand")) && (code.includes(".description(") || code.includes(".option(")) && (code.includes(".action(") || code.includes("async"));
|
|
188
189
|
return isCliFile && hasCommandPattern;
|
|
189
190
|
},
|
|
190
191
|
severity: import_core3.Severity.Info,
|
|
191
192
|
reason: "CLI command definitions follow standard Commander.js patterns and are intentionally similar",
|
|
192
193
|
suggestion: "Command boilerplate duplication is acceptable for CLI interfaces"
|
|
194
|
+
},
|
|
195
|
+
// DynamoDB Single-Table Design - Standard single-table patterns with prefixed keys
|
|
196
|
+
{
|
|
197
|
+
name: "dynamodb-single-table",
|
|
198
|
+
detect: (file, code) => {
|
|
199
|
+
const hasDynamoDBPattern = code.includes("docClient") || code.includes("dynamodb") || code.includes("DynamoDB") || code.includes("queryItems") || code.includes("putItem") || code.includes("getItem") || code.includes("updateItem") || code.includes("deleteItem");
|
|
200
|
+
const hasKeyPrefix = code.includes("userId:") && code.includes("#") || code.includes("pk:") && code.includes("#") || code.includes("Key:") && code.includes("#") || /[A-Z]+#/.test(code);
|
|
201
|
+
const hasSingleTablePattern = code.includes("KeyConditionExpression") || code.includes("pk =") || code.includes("sk =") || code.includes("userId") && code.includes("timestamp");
|
|
202
|
+
return hasDynamoDBPattern && (hasKeyPrefix || hasSingleTablePattern);
|
|
203
|
+
},
|
|
204
|
+
severity: import_core3.Severity.Info,
|
|
205
|
+
reason: "DynamoDB single-table design with prefixed keys is a standard pattern for efficient data access",
|
|
206
|
+
suggestion: "Single-table query patterns are intentionally similar and should not be refactored"
|
|
207
|
+
},
|
|
208
|
+
// CLI Main Function Boilerplate - Standard argument parsing patterns
|
|
209
|
+
{
|
|
210
|
+
name: "cli-main-boilerplate",
|
|
211
|
+
detect: (file, code) => {
|
|
212
|
+
const basename = file.split("/").pop() || "";
|
|
213
|
+
const isCliFile = file.includes("/cli/") || file.includes("/commands/") || basename.startsWith("cli") || basename.endsWith(".cli.ts") || basename.endsWith(".cli.js");
|
|
214
|
+
const hasMainFunction = code.includes("function main()") || code.includes("async function main()") || code.includes("const main =") || code.includes("main()");
|
|
215
|
+
const hasArgParsing = code.includes("process.argv") || code.includes("yargs") || code.includes("commander") || code.includes("minimist") || code.includes(".parse(") || code.includes("args") && code.includes("._");
|
|
216
|
+
return isCliFile && hasMainFunction && hasArgParsing;
|
|
217
|
+
},
|
|
218
|
+
severity: import_core3.Severity.Info,
|
|
219
|
+
reason: "CLI main functions with argument parsing follow standard boilerplate patterns",
|
|
220
|
+
suggestion: "CLI argument parsing boilerplate is acceptable and should not be flagged as duplication"
|
|
193
221
|
}
|
|
194
222
|
];
|
|
195
223
|
|
|
196
224
|
// src/rules/categories/logic-rules.ts
|
|
197
225
|
var import_core4 = require("@aiready/core");
|
|
198
226
|
var LOGIC_RULES = [
|
|
227
|
+
// Enum Semantic Difference - Different enum names indicate different semantic meanings
|
|
228
|
+
{
|
|
229
|
+
name: "enum-semantic-difference",
|
|
230
|
+
detect: (file, code) => {
|
|
231
|
+
const enumRegex = /(?:export\s+)?(?:const\s+)?enum\s+([A-Z][a-zA-Z0-9]*)/g;
|
|
232
|
+
const enums = [];
|
|
233
|
+
let match;
|
|
234
|
+
while ((match = enumRegex.exec(code)) !== null) {
|
|
235
|
+
enums.push(match[1]);
|
|
236
|
+
}
|
|
237
|
+
return enums.length > 0;
|
|
238
|
+
},
|
|
239
|
+
severity: import_core4.Severity.Info,
|
|
240
|
+
reason: "Enums with different names represent different semantic domain concepts, even if they share similar values",
|
|
241
|
+
suggestion: "Different enums (e.g., EscalationPriority vs HealthSeverity) serve different purposes and should not be merged"
|
|
242
|
+
},
|
|
243
|
+
// Enum Value Similarity - Common enum values like LOW, MEDIUM, HIGH are standard
|
|
244
|
+
{
|
|
245
|
+
name: "enum-value-similarity",
|
|
246
|
+
detect: (file, code) => {
|
|
247
|
+
const hasCommonEnumValues = (code.includes("LOW = 'low'") || code.includes("LOW = 0") || code.includes("LOW = 'LOW'")) && (code.includes("HIGH = 'high'") || code.includes("HIGH = 2") || code.includes("HIGH = 'HIGH'")) && (code.includes("MEDIUM = 'medium'") || code.includes("MEDIUM = 1") || code.includes("MEDIUM = 'MEDIUM'"));
|
|
248
|
+
const isEnumDefinition = /(?:export\s+)?(?:const\s+)?enum\s+/.test(code) || code.includes("enum ") && code.includes("{") && code.includes("}");
|
|
249
|
+
return hasCommonEnumValues && isEnumDefinition;
|
|
250
|
+
},
|
|
251
|
+
severity: import_core4.Severity.Info,
|
|
252
|
+
reason: "Common enum values (LOW, MEDIUM, HIGH, CRITICAL) are standard patterns used across different domain enums",
|
|
253
|
+
suggestion: "Enum value similarity is expected for severity/priority enums and should not be flagged as duplication"
|
|
254
|
+
},
|
|
255
|
+
// Re-export / Barrel files - Intentional API surface consolidation
|
|
256
|
+
{
|
|
257
|
+
name: "re-export-files",
|
|
258
|
+
detect: (file, code) => {
|
|
259
|
+
const isIndexFile = file.endsWith("/index.ts") || file.endsWith("/index.js") || file.endsWith("/index.tsx") || file.endsWith("/index.jsx");
|
|
260
|
+
const lines = code.split("\n").filter((l) => l.trim());
|
|
261
|
+
if (lines.length === 0) return false;
|
|
262
|
+
const reExportLines = lines.filter(
|
|
263
|
+
(l) => /^export\s+(\{[^}]+\}|\*)\s+from\s+/.test(l.trim()) || /^export\s+\*\s+as\s+\w+\s+from\s+/.test(l.trim())
|
|
264
|
+
).length;
|
|
265
|
+
return isIndexFile && reExportLines > 0 && reExportLines / lines.length > 0.5;
|
|
266
|
+
},
|
|
267
|
+
severity: import_core4.Severity.Info,
|
|
268
|
+
reason: "Barrel/index files intentionally re-export for API surface consolidation",
|
|
269
|
+
suggestion: "Re-exports in barrel files are expected and not true duplication"
|
|
270
|
+
},
|
|
199
271
|
// Type Definitions - Duplication for type safety and module independence
|
|
200
272
|
{
|
|
201
273
|
name: "type-definitions",
|
|
@@ -209,6 +281,20 @@ var LOGIC_RULES = [
|
|
|
209
281
|
reason: "Type/interface definitions are intentionally duplicated for module independence",
|
|
210
282
|
suggestion: "Extract to shared types package only if causing maintenance burden"
|
|
211
283
|
},
|
|
284
|
+
// Cross-Package Type Definitions - Different packages may have similar types
|
|
285
|
+
{
|
|
286
|
+
name: "cross-package-types",
|
|
287
|
+
detect: (file, code) => {
|
|
288
|
+
const hasTypeDefinition = code.includes("interface ") || code.includes("type ") || code.includes("enum ");
|
|
289
|
+
const isPackageOrApp = file.includes("/packages/") || file.includes("/apps/") || file.includes("/core/");
|
|
290
|
+
const packageMatch = file.match(/\/(packages|apps|core)\/([^/]+)\//);
|
|
291
|
+
const hasPackageStructure = packageMatch !== null;
|
|
292
|
+
return hasTypeDefinition && isPackageOrApp && hasPackageStructure;
|
|
293
|
+
},
|
|
294
|
+
severity: import_core4.Severity.Info,
|
|
295
|
+
reason: "Types in different packages/modules are often intentionally similar for module independence",
|
|
296
|
+
suggestion: "Cross-package type duplication is acceptable for decoupled module architecture"
|
|
297
|
+
},
|
|
212
298
|
// Utility Functions - Small helpers in dedicated utility files
|
|
213
299
|
{
|
|
214
300
|
name: "utility-functions",
|
|
@@ -291,6 +377,22 @@ var LOGIC_RULES = [
|
|
|
291
377
|
severity: import_core4.Severity.Info,
|
|
292
378
|
reason: "Validation functions are inherently similar and often intentionally duplicated for domain clarity",
|
|
293
379
|
suggestion: "Consider extracting to shared validators only if validation logic becomes complex"
|
|
380
|
+
},
|
|
381
|
+
// Singleton Getter Pattern - Standard singleton initialization pattern
|
|
382
|
+
{
|
|
383
|
+
name: "singleton-getter",
|
|
384
|
+
detect: (file, code) => {
|
|
385
|
+
const hasSingletonGetter = /(?:export\s+)?(?:async\s+)?function\s+get[A-Z][a-zA-Z0-9]*\s*\(/.test(
|
|
386
|
+
code
|
|
387
|
+
) || /(?:export\s+)?const\s+get[A-Z][a-zA-Z0-9]*\s*=\s*(?:async\s+)?\(\)\s*=>/.test(
|
|
388
|
+
code
|
|
389
|
+
);
|
|
390
|
+
const hasSingletonPattern = code.includes("if (!") && code.includes("instance") && code.includes(" = ") || code.includes("if (!_") && code.includes(" = new ") || code.includes("if (") && code.includes(" === null") && code.includes(" = new ");
|
|
391
|
+
return hasSingletonGetter && hasSingletonPattern;
|
|
392
|
+
},
|
|
393
|
+
severity: import_core4.Severity.Info,
|
|
394
|
+
reason: "Singleton getter functions follow standard initialization pattern and are intentionally similar",
|
|
395
|
+
suggestion: "Singleton getters are boilerplate and acceptable duplication for lazy initialization"
|
|
294
396
|
}
|
|
295
397
|
];
|
|
296
398
|
|
|
@@ -759,6 +861,22 @@ function getRefactoringSuggestion(patternType, similarity) {
|
|
|
759
861
|
return baseMessages[patternType] + urgency;
|
|
760
862
|
}
|
|
761
863
|
function generateSummary(results) {
|
|
864
|
+
if (!Array.isArray(results)) {
|
|
865
|
+
return {
|
|
866
|
+
totalPatterns: 0,
|
|
867
|
+
totalTokenCost: 0,
|
|
868
|
+
patternsByType: {
|
|
869
|
+
"api-handler": 0,
|
|
870
|
+
validator: 0,
|
|
871
|
+
utility: 0,
|
|
872
|
+
"class-method": 0,
|
|
873
|
+
component: 0,
|
|
874
|
+
function: 0,
|
|
875
|
+
unknown: 0
|
|
876
|
+
},
|
|
877
|
+
topDuplicates: []
|
|
878
|
+
};
|
|
879
|
+
}
|
|
762
880
|
const allIssues = results.flatMap((r) => r.issues || []);
|
|
763
881
|
const totalTokenCost = results.reduce(
|
|
764
882
|
(sum, r) => sum + (r.metrics?.tokenCost || 0),
|
|
@@ -847,7 +965,35 @@ function calculateSeverity2(similarity) {
|
|
|
847
965
|
// src/scoring.ts
|
|
848
966
|
var import_core10 = require("@aiready/core");
|
|
849
967
|
function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
|
|
850
|
-
const actionableDuplicates = duplicates.filter((d) =>
|
|
968
|
+
const actionableDuplicates = duplicates.filter((d) => {
|
|
969
|
+
if (d.severity === "info") return false;
|
|
970
|
+
const acceptableRules = [
|
|
971
|
+
// Logic rules (logic-rules.ts)
|
|
972
|
+
"type-definitions",
|
|
973
|
+
"utility-functions",
|
|
974
|
+
"shared-hooks",
|
|
975
|
+
"score-helpers",
|
|
976
|
+
"visualization-handlers",
|
|
977
|
+
"switch-helpers",
|
|
978
|
+
"common-api-functions",
|
|
979
|
+
"validation-functions",
|
|
980
|
+
// Infrastructure rules (infra-rules.ts)
|
|
981
|
+
"config-files",
|
|
982
|
+
"migration-scripts",
|
|
983
|
+
"tool-implementations",
|
|
984
|
+
"cli-command-definitions",
|
|
985
|
+
// Web rules (web-rules.ts)
|
|
986
|
+
"templates",
|
|
987
|
+
"common-ui-handlers",
|
|
988
|
+
"nextjs-route-handlers",
|
|
989
|
+
// Test rules (test-rules.ts)
|
|
990
|
+
"test-fixtures",
|
|
991
|
+
"e2e-page-objects",
|
|
992
|
+
"mock-data"
|
|
993
|
+
];
|
|
994
|
+
if (d.matchedRule && acceptableRules.includes(d.matchedRule)) return false;
|
|
995
|
+
return true;
|
|
996
|
+
});
|
|
851
997
|
const totalDuplicates = actionableDuplicates.length;
|
|
852
998
|
const totalTokenCost = actionableDuplicates.reduce(
|
|
853
999
|
(sum, d) => sum + d.tokenCost,
|
package/dist/index.mjs
CHANGED
|
@@ -12,16 +12,16 @@ import {
|
|
|
12
12
|
getSmartDefaults,
|
|
13
13
|
groupDuplicatesByFilePair,
|
|
14
14
|
logConfiguration
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-WQX7IHAN.mjs";
|
|
16
16
|
import {
|
|
17
17
|
detectDuplicatePatterns
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-JWP5TCDM.mjs";
|
|
19
19
|
import {
|
|
20
20
|
filterBySeverity
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-KDXWIT6W.mjs";
|
|
22
22
|
import {
|
|
23
23
|
calculatePatternScore
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-G3GZFYRI.mjs";
|
|
25
25
|
|
|
26
26
|
// src/index.ts
|
|
27
27
|
import { ToolRegistry } from "@aiready/core";
|
|
@@ -27,7 +27,35 @@ module.exports = __toCommonJS(scoring_entry_exports);
|
|
|
27
27
|
// src/scoring.ts
|
|
28
28
|
var import_core = require("@aiready/core");
|
|
29
29
|
function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
|
|
30
|
-
const actionableDuplicates = duplicates.filter((d) =>
|
|
30
|
+
const actionableDuplicates = duplicates.filter((d) => {
|
|
31
|
+
if (d.severity === "info") return false;
|
|
32
|
+
const acceptableRules = [
|
|
33
|
+
// Logic rules (logic-rules.ts)
|
|
34
|
+
"type-definitions",
|
|
35
|
+
"utility-functions",
|
|
36
|
+
"shared-hooks",
|
|
37
|
+
"score-helpers",
|
|
38
|
+
"visualization-handlers",
|
|
39
|
+
"switch-helpers",
|
|
40
|
+
"common-api-functions",
|
|
41
|
+
"validation-functions",
|
|
42
|
+
// Infrastructure rules (infra-rules.ts)
|
|
43
|
+
"config-files",
|
|
44
|
+
"migration-scripts",
|
|
45
|
+
"tool-implementations",
|
|
46
|
+
"cli-command-definitions",
|
|
47
|
+
// Web rules (web-rules.ts)
|
|
48
|
+
"templates",
|
|
49
|
+
"common-ui-handlers",
|
|
50
|
+
"nextjs-route-handlers",
|
|
51
|
+
// Test rules (test-rules.ts)
|
|
52
|
+
"test-fixtures",
|
|
53
|
+
"e2e-page-objects",
|
|
54
|
+
"mock-data"
|
|
55
|
+
];
|
|
56
|
+
if (d.matchedRule && acceptableRules.includes(d.matchedRule)) return false;
|
|
57
|
+
return true;
|
|
58
|
+
});
|
|
31
59
|
const totalDuplicates = actionableDuplicates.length;
|
|
32
60
|
const totalTokenCost = actionableDuplicates.reduce(
|
|
33
61
|
(sum, d) => sum + d.tokenCost,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/pattern-detect",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.15",
|
|
4
4
|
"description": "Semantic duplicate pattern detection for AI-generated code - finds similar implementations that waste AI context tokens",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -56,16 +56,16 @@
|
|
|
56
56
|
"license": "MIT",
|
|
57
57
|
"repository": {
|
|
58
58
|
"type": "git",
|
|
59
|
-
"url": "https://github.com/
|
|
59
|
+
"url": "https://github.com/getaiready/aiready-pattern-detect.git"
|
|
60
60
|
},
|
|
61
|
-
"homepage": "https://github.com/
|
|
61
|
+
"homepage": "https://github.com/getaiready/aiready-pattern-detect",
|
|
62
62
|
"bugs": {
|
|
63
|
-
"url": "https://github.com/
|
|
63
|
+
"url": "https://github.com/getaiready/aiready-pattern-detect/issues"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"commander": "^14.0.0",
|
|
67
67
|
"chalk": "^5.3.0",
|
|
68
|
-
"@aiready/core": "0.24.
|
|
68
|
+
"@aiready/core": "0.24.16"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"tsup": "^8.3.5",
|