@aiready/pattern-detect 0.17.14 → 0.17.16
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.d.mts +2 -2
- package/dist/analyzer-entry/index.d.ts +2 -2
- package/dist/analyzer-entry/index.js +391 -89
- package/dist/analyzer-entry/index.mjs +4 -4
- package/dist/chunk-3LMYFYWG.mjs +514 -0
- package/dist/chunk-4SKGAZEW.mjs +514 -0
- package/dist/chunk-4YXKUW4P.mjs +143 -0
- package/dist/chunk-5A3ULAQ5.mjs +571 -0
- package/dist/chunk-5FACKJ7M.mjs +519 -0
- package/dist/chunk-6B72OWZA.mjs +143 -0
- package/dist/chunk-6SHBBRHF.mjs +600 -0
- package/dist/chunk-ATXO4JL7.mjs +404 -0
- package/dist/chunk-BKSIA7A2.mjs +516 -0
- package/dist/chunk-CM5YJR7G.mjs +516 -0
- package/dist/chunk-F42Q2M4O.mjs +143 -0
- package/dist/chunk-FSXOU23F.mjs +620 -0
- package/dist/chunk-GUYQI3AF.mjs +514 -0
- package/dist/chunk-H2TGXGMX.mjs +587 -0
- package/dist/chunk-JWP5TCDM.mjs +143 -0
- package/dist/chunk-KDXWIT6W.mjs +408 -0
- package/dist/chunk-KMAOEVRS.mjs +150 -0
- package/dist/chunk-KZQXBBR3.mjs +143 -0
- package/dist/chunk-NVV4UFIV.mjs +514 -0
- package/dist/chunk-NWG2ZIGX.mjs +146 -0
- package/dist/chunk-OFVJFGQW.mjs +514 -0
- package/dist/chunk-PCCZREHY.mjs +143 -0
- package/dist/chunk-PFA2DO73.mjs +392 -0
- package/dist/chunk-PQS5ACTN.mjs +516 -0
- package/dist/chunk-TVE75IDM.mjs +143 -0
- package/dist/chunk-UDOGQ42Q.mjs +603 -0
- package/dist/chunk-UFI4UDQI.mjs +514 -0
- package/dist/chunk-UXV57HN3.mjs +144 -0
- package/dist/chunk-VC2BOV6R.mjs +143 -0
- package/dist/chunk-VI2OVG73.mjs +514 -0
- package/dist/chunk-VKGYNHFY.mjs +514 -0
- package/dist/chunk-WBLZYAQ2.mjs +518 -0
- package/dist/chunk-WFVXMMB3.mjs +143 -0
- package/dist/chunk-WQC43BIO.mjs +516 -0
- package/dist/chunk-WQX7IHAN.mjs +514 -0
- package/dist/chunk-WTAIM3SG.mjs +146 -0
- package/dist/chunk-XC7U55PE.mjs +514 -0
- package/dist/chunk-XR373Q6G.mjs +146 -0
- package/dist/chunk-XWIBTD67.mjs +620 -0
- package/dist/chunk-YUQ2VQVJ.mjs +514 -0
- package/dist/chunk-Z4NOH52X.mjs +143 -0
- package/dist/cli.js +395 -93
- package/dist/cli.mjs +8 -8
- package/dist/context-rules-entry/index.js +385 -88
- package/dist/context-rules-entry/index.mjs +1 -1
- package/dist/detector-entry/index.d.mts +2 -2
- package/dist/detector-entry/index.d.ts +2 -2
- package/dist/detector-entry/index.js +389 -89
- package/dist/detector-entry/index.mjs +2 -2
- package/dist/index-BGvkJ9j1.d.mts +136 -0
- package/dist/index-BJq32qmj.d.mts +137 -0
- package/dist/index-BpoJSgX-.d.mts +136 -0
- package/dist/index-C7qLPKmH.d.ts +150 -0
- package/dist/index-CThnG9hv.d.ts +155 -0
- package/dist/index-D0Hpg9nN.d.mts +150 -0
- package/dist/index-DN6XpBOW.d.mts +155 -0
- package/dist/index-F8xqZ2PS.d.ts +136 -0
- package/dist/index-HNhDr6CV.d.ts +137 -0
- package/dist/index-ux0Wo8Ps.d.ts +136 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +393 -91
- package/dist/index.mjs +6 -6
- package/dist/scoring-entry/index.d.mts +1 -1
- package/dist/scoring-entry/index.d.ts +1 -1
- package/dist/scoring-entry/index.js +2 -2
- package/dist/scoring-entry/index.mjs +1 -1
- package/dist/types-tgrmUrHE.d.mts +37 -0
- package/dist/types-tgrmUrHE.d.ts +37 -0
- package/package.json +8 -6
package/dist/index.js
CHANGED
|
@@ -191,124 +191,421 @@ var INFRA_RULES = [
|
|
|
191
191
|
severity: import_core3.Severity.Info,
|
|
192
192
|
reason: "CLI command definitions follow standard Commander.js patterns and are intentionally similar",
|
|
193
193
|
suggestion: "Command boilerplate duplication is acceptable for CLI interfaces"
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
// src/rules/categories/logic-rules.ts
|
|
198
|
-
var import_core4 = require("@aiready/core");
|
|
199
|
-
var LOGIC_RULES = [
|
|
200
|
-
// Re-export / Barrel files - Intentional API surface consolidation
|
|
194
|
+
},
|
|
195
|
+
// DynamoDB Single-Table Design - Standard single-table patterns with prefixed keys
|
|
201
196
|
{
|
|
202
|
-
name: "
|
|
197
|
+
name: "dynamodb-single-table",
|
|
203
198
|
detect: (file, code) => {
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
(l) => /^export\s+(\{[^}]+\}|\*)\s+from\s+/.test(l.trim()) || /^export\s+\*\s+as\s+\w+\s+from\s+/.test(l.trim())
|
|
209
|
-
).length;
|
|
210
|
-
return isIndexFile && reExportLines > 0 && reExportLines / lines.length > 0.5;
|
|
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);
|
|
211
203
|
},
|
|
212
|
-
severity:
|
|
213
|
-
reason: "
|
|
214
|
-
suggestion: "
|
|
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"
|
|
215
207
|
},
|
|
216
|
-
//
|
|
208
|
+
// CLI Main Function Boilerplate - Standard argument parsing patterns
|
|
217
209
|
{
|
|
218
|
-
name: "
|
|
210
|
+
name: "cli-main-boilerplate",
|
|
219
211
|
detect: (file, code) => {
|
|
220
|
-
const
|
|
221
|
-
const
|
|
222
|
-
const
|
|
223
|
-
|
|
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;
|
|
224
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"
|
|
221
|
+
}
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
// src/rules/categories/logic/file-detectors.ts
|
|
225
|
+
var FileDetectors = {
|
|
226
|
+
isIndexFile: (file) => file.endsWith("/index.ts") || file.endsWith("/index.js") || file.endsWith("/index.tsx") || file.endsWith("/index.jsx"),
|
|
227
|
+
isTypeFile: (file) => file.endsWith(".d.ts") || file.includes("/types/"),
|
|
228
|
+
isUtilFile: (file) => file.endsWith(".util.ts") || file.endsWith(".helper.ts") || file.endsWith(".utils.ts"),
|
|
229
|
+
isHookFile: (file) => file.includes("/hooks/") || file.endsWith(".hook.ts") || file.endsWith(".hook.tsx"),
|
|
230
|
+
isHelperFile: (file) => file.includes("/utils/") || file.includes("/helpers/") || file.endsWith(".util.ts"),
|
|
231
|
+
isVizFile: (file) => file.includes("/visualizer/") || file.includes("/charts/") || file.includes("GraphCanvas") || file.includes("ForceDirected"),
|
|
232
|
+
isApiFile: (file) => file.includes("/api/") || file.includes("/lib/") || file.includes("/utils/") || file.endsWith(".ts"),
|
|
233
|
+
isPackageOrApp: (file) => file.includes("/packages/") || file.includes("/apps/") || file.includes("/core/"),
|
|
234
|
+
getPackageName: (file) => {
|
|
235
|
+
const match = file.match(/\/(packages|apps|core)\/([^/]+)\//);
|
|
236
|
+
return match?.[2] || null;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// src/rules/categories/logic/keyword-lists.ts
|
|
241
|
+
var VALIDATION_KEYWORDS = [
|
|
242
|
+
"isValid",
|
|
243
|
+
"validate",
|
|
244
|
+
"checkValid",
|
|
245
|
+
"isEmail",
|
|
246
|
+
"isPhone",
|
|
247
|
+
"isUrl",
|
|
248
|
+
"isNumeric",
|
|
249
|
+
"isAlpha",
|
|
250
|
+
"isAlphanumeric",
|
|
251
|
+
"isEmpty",
|
|
252
|
+
"isNotEmpty",
|
|
253
|
+
"isRequired",
|
|
254
|
+
"isOptional"
|
|
255
|
+
];
|
|
256
|
+
var HOOK_KEYWORDS = [
|
|
257
|
+
"function use",
|
|
258
|
+
"export function use",
|
|
259
|
+
"const use",
|
|
260
|
+
"export const use"
|
|
261
|
+
];
|
|
262
|
+
var VIZ_EVENT_KEYWORDS = ["dragstarted", "dragged", "dragended"];
|
|
263
|
+
var VIZ_LIB_KEYWORDS = ["simulation", "d3.", "alphaTarget"];
|
|
264
|
+
var ICON_HELPER_KEYWORDS = [
|
|
265
|
+
"getIcon",
|
|
266
|
+
"getColor",
|
|
267
|
+
"getLabel",
|
|
268
|
+
"getRating"
|
|
269
|
+
];
|
|
270
|
+
var UTIL_KEYWORDS = [
|
|
271
|
+
"format",
|
|
272
|
+
"parse",
|
|
273
|
+
"sanitize",
|
|
274
|
+
"normalize",
|
|
275
|
+
"convert",
|
|
276
|
+
"utility",
|
|
277
|
+
"helper"
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
// src/rules/categories/logic/detectors.ts
|
|
281
|
+
var isEnumDefinition = (code) => /(?:export\s+)?(?:const\s+)?enum\s+/.test(code) || code.includes("enum ") && code.includes("{") && code.includes("}");
|
|
282
|
+
var hasSingletonGetter = (code) => /(?:export\s+)?(?:async\s+)?function\s+get[A-Z][a-zA-Z0-9]*\s*\(/.test(
|
|
283
|
+
code
|
|
284
|
+
) || /(?:export\s+)?const\s+get[A-Z][a-zA-Z0-9]*\s*=\s*(?:async\s+)?\(\)\s*=>/.test(
|
|
285
|
+
code
|
|
286
|
+
);
|
|
287
|
+
var hasSingletonPattern = (code) => code.includes("if (!") && code.includes("instance") && code.includes(" = ") || code.includes("if (!_") && code.includes(" = new ");
|
|
288
|
+
var hasReExportPattern = (code) => {
|
|
289
|
+
const lines = code.split("\n").filter((l) => l.trim());
|
|
290
|
+
if (lines.length === 0) return false;
|
|
291
|
+
const reExportLines = lines.filter(
|
|
292
|
+
(l) => /^export\s+(\{[^}]+\}|\*)\s+from\s+/.test(l.trim()) || /^export\s+\*\s+as\s+\w+\s+from\s+/.test(l.trim())
|
|
293
|
+
).length;
|
|
294
|
+
return reExportLines > 0 && reExportLines / lines.length > 0.5;
|
|
295
|
+
};
|
|
296
|
+
var isInterfaceOnlySnippet = (code) => {
|
|
297
|
+
const hasInterface = code.includes("interface ");
|
|
298
|
+
const hasType = code.includes("type ");
|
|
299
|
+
const hasEnum = code.includes("enum ");
|
|
300
|
+
const hasNoImpl = ![
|
|
301
|
+
"function ",
|
|
302
|
+
"class ",
|
|
303
|
+
"const ",
|
|
304
|
+
"let ",
|
|
305
|
+
"var ",
|
|
306
|
+
"export default",
|
|
307
|
+
"export {"
|
|
308
|
+
].some((kw) => code.includes(kw));
|
|
309
|
+
return (hasInterface || hasType || hasEnum) && hasNoImpl;
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// src/rules/categories/logic/code-patterns.ts
|
|
313
|
+
var COMMON_ENUM_PATTERNS = [
|
|
314
|
+
["LOW", ["'low'", "0", "'LOW'"]],
|
|
315
|
+
["HIGH", ["'high'", "2", "'HIGH'"]],
|
|
316
|
+
["MEDIUM", ["'medium'", "1", "'MEDIUM'"]]
|
|
317
|
+
];
|
|
318
|
+
var hasEnumValue = (code, enumName, variants) => variants.some((v) => code.includes(`${enumName} = ${v}`));
|
|
319
|
+
var CodePatterns = {
|
|
320
|
+
// Enum patterns
|
|
321
|
+
hasCommonEnumValues: (code) => COMMON_ENUM_PATTERNS.every(
|
|
322
|
+
([name, variants]) => hasEnumValue(code, name, variants)
|
|
323
|
+
),
|
|
324
|
+
isEnumDefinition,
|
|
325
|
+
// Type patterns - helpers for checking type-only definitions
|
|
326
|
+
hasTypeDefinition: (code) => code.includes("interface ") || code.includes("type ") || code.includes("enum "),
|
|
327
|
+
isTypeOnlyFile: (code) => {
|
|
328
|
+
const hasTypes = CodePatterns.hasTypeDefinition(code);
|
|
329
|
+
const hasNoImpl = ![
|
|
330
|
+
"function ",
|
|
331
|
+
"class ",
|
|
332
|
+
"const ",
|
|
333
|
+
"let ",
|
|
334
|
+
"export default"
|
|
335
|
+
].some((kw) => code.includes(kw));
|
|
336
|
+
return hasTypes && hasNoImpl;
|
|
337
|
+
},
|
|
338
|
+
hasOnlyTypeDefinitions: (code) => {
|
|
339
|
+
const hasTypes = CodePatterns.hasTypeDefinition(code);
|
|
340
|
+
const hasNoImpl = ![
|
|
341
|
+
"function ",
|
|
342
|
+
"class ",
|
|
343
|
+
"const ",
|
|
344
|
+
"let ",
|
|
345
|
+
"var ",
|
|
346
|
+
"export default",
|
|
347
|
+
"export {"
|
|
348
|
+
].some((kw) => code.includes(kw));
|
|
349
|
+
return hasTypes && hasNoImpl;
|
|
350
|
+
},
|
|
351
|
+
// Utility patterns - function group patterns
|
|
352
|
+
hasUtilPattern: (code) => CodePatterns.hasFunctionGroup(code, "") || CodePatterns.hasKeywordGroup(code, UTIL_KEYWORDS),
|
|
353
|
+
hasFunctionGroup: (code, prefix) => {
|
|
354
|
+
const regex = new RegExp(
|
|
355
|
+
`${prefix}(format|parse|sanitize|normalize|convert)`,
|
|
356
|
+
"i"
|
|
357
|
+
);
|
|
358
|
+
return regex.test(code);
|
|
359
|
+
},
|
|
360
|
+
// Pattern checkers using keyword lists
|
|
361
|
+
hasKeywordGroup: (code, keywords) => keywords.some((kw) => code.includes(kw)),
|
|
362
|
+
// Validation patterns - unified keyword check
|
|
363
|
+
hasValidationPattern: (code) => CodePatterns.hasKeywordGroup(code, VALIDATION_KEYWORDS),
|
|
364
|
+
// Hook patterns - React/Vue hooks
|
|
365
|
+
hasHookPattern: (code) => CodePatterns.hasKeywordGroup(code, HOOK_KEYWORDS),
|
|
366
|
+
// Score/Rating patterns
|
|
367
|
+
hasScorePattern: (code) => (code.includes("if (score >=") || code.includes("if (score >")) && code.includes("return") && code.includes("'") && code.split("if (score").length >= 3,
|
|
368
|
+
// Visualization patterns - D3/Canvas
|
|
369
|
+
hasVizPattern: (code) => CodePatterns.hasKeywordGroup(code, VIZ_EVENT_KEYWORDS) && CodePatterns.hasKeywordGroup(code, VIZ_LIB_KEYWORDS),
|
|
370
|
+
// Switch/Icon patterns
|
|
371
|
+
hasSwitchPattern: (code) => code.includes("switch (") && code.includes("case '") && code.includes("return") && code.split("case ").length >= 4,
|
|
372
|
+
hasIconPattern: (code) => CodePatterns.hasKeywordGroup(code, ICON_HELPER_KEYWORDS),
|
|
373
|
+
// Singleton patterns
|
|
374
|
+
hasSingletonGetter,
|
|
375
|
+
hasSingletonPattern,
|
|
376
|
+
// Re-export patterns
|
|
377
|
+
hasReExportPattern,
|
|
378
|
+
// Interface-only snippets
|
|
379
|
+
isInterfaceOnlySnippet
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// src/rules/categories/logic/api-patterns.ts
|
|
383
|
+
var API_PATTERNS = {
|
|
384
|
+
stripe: {
|
|
385
|
+
functions: ["getStripe"],
|
|
386
|
+
keywords: ["process.env.STRIPE_SECRET_KEY"]
|
|
387
|
+
},
|
|
388
|
+
userManagement: {
|
|
389
|
+
functions: ["getUserByEmail"],
|
|
390
|
+
keywords: ["queryItems"]
|
|
391
|
+
},
|
|
392
|
+
userUpdate: {
|
|
393
|
+
functions: ["updateUser"],
|
|
394
|
+
keywords: ["buildUpdateExpression"]
|
|
395
|
+
},
|
|
396
|
+
repositoryListing: {
|
|
397
|
+
functions: ["listUserRepositories", "listTeamRepositories"],
|
|
398
|
+
keywords: ["queryItems"]
|
|
399
|
+
},
|
|
400
|
+
remediationQueries: {
|
|
401
|
+
functions: ["getRemediation"],
|
|
402
|
+
keywords: ["queryItems"]
|
|
403
|
+
},
|
|
404
|
+
keyFormatting: {
|
|
405
|
+
functions: ["formatBreakdownKey"],
|
|
406
|
+
keywords: [".replace(/([A-Z])/g"]
|
|
407
|
+
},
|
|
408
|
+
dynamoDbQueries: {
|
|
409
|
+
functions: ["queryItems"],
|
|
410
|
+
keywords: ["KeyConditionExpression"]
|
|
411
|
+
},
|
|
412
|
+
itemOperations: {
|
|
413
|
+
functions: ["putItem"],
|
|
414
|
+
keywords: ["createdAt"]
|
|
415
|
+
},
|
|
416
|
+
itemUpdates: {
|
|
417
|
+
functions: ["updateItem"],
|
|
418
|
+
keywords: ["buildUpdateExpression"]
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
var ApiPatterns = {
|
|
422
|
+
/**
|
|
423
|
+
* Check if code contains a common API pattern
|
|
424
|
+
* Reduces from 11 separate || conditions to single call
|
|
425
|
+
*/
|
|
426
|
+
hasCommonApiPattern: (code) => {
|
|
427
|
+
for (const pattern of Object.values(API_PATTERNS)) {
|
|
428
|
+
const hasFunctions = pattern.functions.some((fn) => code.includes(fn));
|
|
429
|
+
const hasKeywords = pattern.keywords.some((kw) => code.includes(kw));
|
|
430
|
+
if (hasFunctions && hasKeywords) {
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return false;
|
|
435
|
+
},
|
|
436
|
+
/**
|
|
437
|
+
* Get all API patterns (useful for documentation/analysis)
|
|
438
|
+
*/
|
|
439
|
+
getPatterns: () => API_PATTERNS,
|
|
440
|
+
/**
|
|
441
|
+
* Check if code matches a specific API pattern by name
|
|
442
|
+
*/
|
|
443
|
+
matchesPattern: (code, patternName) => {
|
|
444
|
+
const pattern = API_PATTERNS[patternName];
|
|
445
|
+
if (!pattern) return false;
|
|
446
|
+
const hasFunctions = pattern.functions.some((fn) => code.includes(fn));
|
|
447
|
+
const hasKeywords = pattern.keywords.some((kw) => code.includes(kw));
|
|
448
|
+
return hasFunctions && hasKeywords;
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
// src/rules/categories/logic/rule-builders.ts
|
|
453
|
+
var import_core4 = require("@aiready/core");
|
|
454
|
+
function createRule(config) {
|
|
455
|
+
return {
|
|
456
|
+
name: config.name,
|
|
457
|
+
detect: config.detect,
|
|
458
|
+
severity: config.severity || import_core4.Severity.Info,
|
|
459
|
+
reason: config.reason,
|
|
460
|
+
suggestion: config.suggestion
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
var RuleTemplates = {
|
|
464
|
+
// Type-related rules
|
|
465
|
+
typeDefinition: {
|
|
225
466
|
severity: import_core4.Severity.Info,
|
|
226
467
|
reason: "Type/interface definitions are intentionally duplicated for module independence",
|
|
227
468
|
suggestion: "Extract to shared types package only if causing maintenance burden"
|
|
228
469
|
},
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
470
|
+
crossPackageType: {
|
|
471
|
+
severity: import_core4.Severity.Info,
|
|
472
|
+
reason: "Types in different packages/modules are often intentionally similar for module independence",
|
|
473
|
+
suggestion: "Cross-package type duplication is acceptable for decoupled module architecture"
|
|
474
|
+
},
|
|
475
|
+
// Pattern rules
|
|
476
|
+
standardPatterns: {
|
|
477
|
+
severity: import_core4.Severity.Info,
|
|
478
|
+
reason: "This pattern is inherently similar and intentionally duplicated across modules",
|
|
479
|
+
suggestion: "Pattern duplication is acceptable for domain clarity and independence"
|
|
480
|
+
},
|
|
481
|
+
// API rules
|
|
482
|
+
commonApiFunction: {
|
|
237
483
|
severity: import_core4.Severity.Info,
|
|
238
|
-
reason: "
|
|
484
|
+
reason: "Common API/utility functions are legitimately duplicated across modules for clarity and independence",
|
|
239
485
|
suggestion: "Consider extracting to shared utilities only if causing significant duplication"
|
|
240
486
|
},
|
|
241
|
-
//
|
|
242
|
-
{
|
|
243
|
-
|
|
487
|
+
// Utility rules
|
|
488
|
+
utilityFunction: {
|
|
489
|
+
severity: import_core4.Severity.Info,
|
|
490
|
+
reason: "Utility functions may be intentionally similar across modules",
|
|
491
|
+
suggestion: "Consider extracting to shared utilities only if causing significant duplication"
|
|
492
|
+
},
|
|
493
|
+
// Boilerplate rules
|
|
494
|
+
boilerplate: {
|
|
495
|
+
severity: import_core4.Severity.Info,
|
|
496
|
+
reason: "This pattern is boilerplate and acceptable duplication",
|
|
497
|
+
suggestion: "Boilerplate reduction is acceptable for standard patterns"
|
|
498
|
+
},
|
|
499
|
+
// Enum rules
|
|
500
|
+
enumSemantic: {
|
|
501
|
+
severity: import_core4.Severity.Info,
|
|
502
|
+
reason: "Enums with different names represent different semantic domain concepts, even if they share similar values",
|
|
503
|
+
suggestion: "Different enums (e.g., EscalationPriority vs HealthSeverity) serve different purposes and should not be merged"
|
|
504
|
+
},
|
|
505
|
+
enumValue: {
|
|
506
|
+
severity: import_core4.Severity.Info,
|
|
507
|
+
reason: "Common enum values (LOW, MEDIUM, HIGH, CRITICAL) are standard patterns used across different domain enums",
|
|
508
|
+
suggestion: "Enum value similarity is expected for severity/priority enums and should not be flagged as duplication"
|
|
509
|
+
},
|
|
510
|
+
// Barrel file rules
|
|
511
|
+
barrelFile: {
|
|
512
|
+
severity: import_core4.Severity.Info,
|
|
513
|
+
reason: "Barrel/index files intentionally re-export for API surface consolidation",
|
|
514
|
+
suggestion: "Re-exports in barrel files are expected and not true duplication"
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
// src/rules/categories/logic-rules.ts
|
|
519
|
+
var LOGIC_RULES = [
|
|
520
|
+
// Enum patterns - identify by semantic meaning and value patterns
|
|
521
|
+
createRule({
|
|
522
|
+
name: "enum-semantic-difference",
|
|
523
|
+
detect: (file, code) => CodePatterns.isEnumDefinition(code),
|
|
524
|
+
...RuleTemplates.enumSemantic
|
|
525
|
+
}),
|
|
526
|
+
createRule({
|
|
527
|
+
name: "enum-value-similarity",
|
|
528
|
+
detect: (file, code) => CodePatterns.hasCommonEnumValues(code) && CodePatterns.isEnumDefinition(code),
|
|
529
|
+
...RuleTemplates.enumValue
|
|
530
|
+
}),
|
|
531
|
+
// Barrel/re-export files - intentional API consolidation
|
|
532
|
+
createRule({
|
|
533
|
+
name: "re-export-files",
|
|
534
|
+
detect: (file, code) => FileDetectors.isIndexFile(file) && CodePatterns.hasReExportPattern(code),
|
|
535
|
+
...RuleTemplates.barrelFile
|
|
536
|
+
}),
|
|
537
|
+
// Type definitions - consolidated rule for both local and cross-package types
|
|
538
|
+
// PHASE 2 CONSOLIDATION: merged type-definitions + cross-package-types
|
|
539
|
+
createRule({
|
|
540
|
+
name: "type-definitions",
|
|
244
541
|
detect: (file, code) => {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
542
|
+
if (FileDetectors.isTypeFile(file)) {
|
|
543
|
+
if (CodePatterns.hasOnlyTypeDefinitions(code)) return true;
|
|
544
|
+
}
|
|
545
|
+
if (CodePatterns.isInterfaceOnlySnippet(code)) return true;
|
|
546
|
+
if (FileDetectors.isPackageOrApp(file) && FileDetectors.getPackageName(file) !== null && CodePatterns.hasTypeDefinition(code)) {
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
549
|
+
return false;
|
|
248
550
|
},
|
|
249
|
-
|
|
551
|
+
reason: "Type/interface definitions are intentionally duplicated for module independence and decoupled module architecture",
|
|
552
|
+
suggestion: "Extract to shared types package only if causing significant maintenance burden or cross-package conflicts"
|
|
553
|
+
}),
|
|
554
|
+
// Utility functions - dedicated utility file patterns
|
|
555
|
+
createRule({
|
|
556
|
+
name: "utility-functions",
|
|
557
|
+
detect: (file, code) => FileDetectors.isUtilFile(file) && CodePatterns.hasUtilPattern(code),
|
|
558
|
+
...RuleTemplates.utilityFunction
|
|
559
|
+
}),
|
|
560
|
+
// React/Vue Hooks - standard hook patterns
|
|
561
|
+
createRule({
|
|
562
|
+
name: "shared-hooks",
|
|
563
|
+
detect: (file, code) => FileDetectors.isHookFile(file) && CodePatterns.hasHookPattern(code),
|
|
250
564
|
reason: "Hooks follow standard patterns and are often intentionally similar across components",
|
|
251
565
|
suggestion: "Consider extracting common hook logic only if hooks become complex"
|
|
252
|
-
},
|
|
253
|
-
// Score/Rating
|
|
254
|
-
{
|
|
566
|
+
}),
|
|
567
|
+
// Score/Rating helpers - common threshold patterns
|
|
568
|
+
createRule({
|
|
255
569
|
name: "score-helpers",
|
|
256
|
-
detect: (file, code) =>
|
|
257
|
-
const isHelperFile = file.includes("/utils/") || file.includes("/helpers/") || file.endsWith(".util.ts");
|
|
258
|
-
const hasScorePattern = (code.includes("if (score >=") || code.includes("if (score >")) && code.includes("return") && code.includes("'") && code.split("if (score").length >= 3;
|
|
259
|
-
return isHelperFile && hasScorePattern;
|
|
260
|
-
},
|
|
261
|
-
severity: import_core4.Severity.Info,
|
|
570
|
+
detect: (file, code) => (FileDetectors.isHelperFile(file) || file.includes("/utils/")) && CodePatterns.hasScorePattern(code),
|
|
262
571
|
reason: "Score/rating helper functions use common threshold patterns that are intentionally similar",
|
|
263
572
|
suggestion: "Score formatting duplication is acceptable for consistent UI display"
|
|
264
|
-
},
|
|
265
|
-
// D3/Canvas
|
|
266
|
-
{
|
|
573
|
+
}),
|
|
574
|
+
// D3/Canvas event handlers - visualization patterns
|
|
575
|
+
createRule({
|
|
267
576
|
name: "visualization-handlers",
|
|
268
|
-
detect: (file, code) =>
|
|
269
|
-
const isVizFile = file.includes("/visualizer/") || file.includes("/charts/") || file.includes("GraphCanvas") || file.includes("ForceDirected");
|
|
270
|
-
const hasVizPattern = (code.includes("dragstarted") || code.includes("dragged") || code.includes("dragended")) && (code.includes("simulation") || code.includes("d3.") || code.includes("alphaTarget"));
|
|
271
|
-
return isVizFile && hasVizPattern;
|
|
272
|
-
},
|
|
273
|
-
severity: import_core4.Severity.Info,
|
|
577
|
+
detect: (file, code) => FileDetectors.isVizFile(file) && CodePatterns.hasVizPattern(code),
|
|
274
578
|
reason: "D3/visualization event handlers follow standard patterns and are intentionally similar",
|
|
275
579
|
suggestion: "Visualization boilerplate duplication is acceptable for interactive charts"
|
|
276
|
-
},
|
|
277
|
-
//
|
|
278
|
-
{
|
|
580
|
+
}),
|
|
581
|
+
// Switch statement helpers - enum-to-value mapping
|
|
582
|
+
createRule({
|
|
279
583
|
name: "switch-helpers",
|
|
280
|
-
detect: (file, code) =>
|
|
281
|
-
const hasSwitchPattern = code.includes("switch (") && code.includes("case '") && code.includes("return") && code.split("case ").length >= 4;
|
|
282
|
-
const hasIconPattern = code.includes("getIcon") || code.includes("getColor") || code.includes("getLabel") || code.includes("getRating");
|
|
283
|
-
return hasSwitchPattern && hasIconPattern;
|
|
284
|
-
},
|
|
285
|
-
severity: import_core4.Severity.Info,
|
|
584
|
+
detect: (file, code) => CodePatterns.hasSwitchPattern(code) && CodePatterns.hasIconPattern(code),
|
|
286
585
|
reason: "Switch statement helpers for enum-to-value mapping are inherently similar",
|
|
287
586
|
suggestion: "Switch duplication is acceptable for mapping enums to display values"
|
|
288
|
-
},
|
|
289
|
-
// Common API/Utility
|
|
290
|
-
{
|
|
587
|
+
}),
|
|
588
|
+
// Common API/Utility functions - legitimately duplicated across modules
|
|
589
|
+
createRule({
|
|
291
590
|
name: "common-api-functions",
|
|
292
|
-
detect: (file, code) =>
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
reason: "Common API/utility functions are legitimately duplicated across modules for clarity and independence",
|
|
299
|
-
suggestion: "Consider extracting to shared utilities only if causing significant duplication"
|
|
300
|
-
},
|
|
301
|
-
// Validation Functions - Inherently similar patterns
|
|
302
|
-
{
|
|
591
|
+
detect: (file, code) => FileDetectors.isApiFile(file) && ApiPatterns.hasCommonApiPattern(code),
|
|
592
|
+
...RuleTemplates.commonApiFunction
|
|
593
|
+
}),
|
|
594
|
+
// Validation functions - inherently similar patterns
|
|
595
|
+
// PHASE 2: Consolidated validation-function branches into single rule
|
|
596
|
+
createRule({
|
|
303
597
|
name: "validation-functions",
|
|
304
|
-
detect: (file, code) =>
|
|
305
|
-
const hasValidationPattern = code.includes("isValid") || code.includes("validate") || code.includes("checkValid") || code.includes("isEmail") || code.includes("isPhone") || code.includes("isUrl") || code.includes("isNumeric") || code.includes("isAlpha") || code.includes("isAlphanumeric") || code.includes("isEmpty") || code.includes("isNotEmpty") || code.includes("isRequired") || code.includes("isOptional");
|
|
306
|
-
return hasValidationPattern;
|
|
307
|
-
},
|
|
308
|
-
severity: import_core4.Severity.Info,
|
|
598
|
+
detect: (file, code) => CodePatterns.hasValidationPattern(code),
|
|
309
599
|
reason: "Validation functions are inherently similar and often intentionally duplicated for domain clarity",
|
|
310
600
|
suggestion: "Consider extracting to shared validators only if validation logic becomes complex"
|
|
311
|
-
}
|
|
601
|
+
}),
|
|
602
|
+
// Singleton getter pattern - standard initialization
|
|
603
|
+
createRule({
|
|
604
|
+
name: "singleton-getter",
|
|
605
|
+
detect: (file, code) => CodePatterns.hasSingletonGetter(code) && CodePatterns.hasSingletonPattern(code),
|
|
606
|
+
reason: "Singleton getter functions follow standard initialization pattern and are intentionally similar",
|
|
607
|
+
suggestion: "Singleton getters are boilerplate and acceptable duplication for lazy initialization"
|
|
608
|
+
})
|
|
312
609
|
];
|
|
313
610
|
|
|
314
611
|
// src/context-rules.ts
|
|
@@ -341,7 +638,7 @@ function calculateSeverity(file1, file2, code, similarity, linesOfCode) {
|
|
|
341
638
|
reason: "Nearly identical code should be consolidated",
|
|
342
639
|
suggestion: "Move to shared utility file"
|
|
343
640
|
};
|
|
344
|
-
} else if (similarity >= 0.85) {
|
|
641
|
+
} else if (similarity >= 0.85 && linesOfCode >= 10) {
|
|
345
642
|
return {
|
|
346
643
|
severity: import_core5.Severity.Major,
|
|
347
644
|
reason: "High similarity indicates significant duplication",
|
|
@@ -395,7 +692,10 @@ async function detectDuplicatePatterns(fileContents, options) {
|
|
|
395
692
|
ignoreWhitelist = []
|
|
396
693
|
} = options;
|
|
397
694
|
const allBlocks = [];
|
|
398
|
-
const
|
|
695
|
+
const regexStr = (f) => f.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, "[^/]*").replace(/\[\^\/\]\*\[\^\/\]\*/g, ".*");
|
|
696
|
+
const excludeRegexes = excludePatterns.map(
|
|
697
|
+
(p) => new RegExp(`${regexStr(p)}$`, "i")
|
|
698
|
+
);
|
|
399
699
|
for (const { file, content } of fileContents) {
|
|
400
700
|
const blocks = extractBlocks(file, content);
|
|
401
701
|
for (const b of blocks) {
|
|
@@ -915,7 +1215,7 @@ function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
|
|
|
915
1215
|
0
|
|
916
1216
|
);
|
|
917
1217
|
const highImpactDuplicates = actionableDuplicates.filter(
|
|
918
|
-
(d) => d.
|
|
1218
|
+
(d) => d.severity === "critical" || d.severity === "major" || d.tokenCost > 1e3
|
|
919
1219
|
).length;
|
|
920
1220
|
if (totalFilesAnalyzed === 0) {
|
|
921
1221
|
return {
|
|
@@ -954,7 +1254,7 @@ function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
|
|
|
954
1254
|
factors.push({
|
|
955
1255
|
name: "High-Impact Patterns",
|
|
956
1256
|
impact: -Math.round(highImpactPenalty),
|
|
957
|
-
description: `${highImpactDuplicates} high-impact duplicates (
|
|
1257
|
+
description: `${highImpactDuplicates} high-impact duplicates (Critical/Major or >1000 tokens)`
|
|
958
1258
|
});
|
|
959
1259
|
} else {
|
|
960
1260
|
factors.push({
|
|
@@ -1031,6 +1331,7 @@ async function analyzePatterns(options) {
|
|
|
1031
1331
|
minClusterTokenCost = 1e3,
|
|
1032
1332
|
minClusterFiles = 3,
|
|
1033
1333
|
excludePatterns = [],
|
|
1334
|
+
excludeFiles = [],
|
|
1034
1335
|
confidenceThreshold = 0,
|
|
1035
1336
|
ignoreWhitelist = [],
|
|
1036
1337
|
...scanOptions
|
|
@@ -1060,6 +1361,7 @@ async function analyzePatterns(options) {
|
|
|
1060
1361
|
maxCandidatesPerBlock,
|
|
1061
1362
|
streamResults,
|
|
1062
1363
|
excludePatterns,
|
|
1364
|
+
excludeFiles,
|
|
1063
1365
|
confidenceThreshold,
|
|
1064
1366
|
ignoreWhitelist,
|
|
1065
1367
|
onProgress: options.onProgress
|
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-WQC43BIO.mjs";
|
|
16
16
|
import {
|
|
17
17
|
detectDuplicatePatterns
|
|
18
|
-
} from "./chunk-
|
|
19
|
-
import {
|
|
20
|
-
calculatePatternScore
|
|
21
|
-
} from "./chunk-G3GZFYRI.mjs";
|
|
18
|
+
} from "./chunk-XR373Q6G.mjs";
|
|
22
19
|
import {
|
|
23
20
|
filterBySeverity
|
|
24
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-XWIBTD67.mjs";
|
|
22
|
+
import {
|
|
23
|
+
calculatePatternScore
|
|
24
|
+
} from "./chunk-UXV57HN3.mjs";
|
|
25
25
|
|
|
26
26
|
// src/index.ts
|
|
27
27
|
import { ToolRegistry } from "@aiready/core";
|
|
@@ -62,7 +62,7 @@ function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
|
|
|
62
62
|
0
|
|
63
63
|
);
|
|
64
64
|
const highImpactDuplicates = actionableDuplicates.filter(
|
|
65
|
-
(d) => d.
|
|
65
|
+
(d) => d.severity === "critical" || d.severity === "major" || d.tokenCost > 1e3
|
|
66
66
|
).length;
|
|
67
67
|
if (totalFilesAnalyzed === 0) {
|
|
68
68
|
return {
|
|
@@ -101,7 +101,7 @@ function calculatePatternScore(duplicates, totalFilesAnalyzed, costConfig) {
|
|
|
101
101
|
factors.push({
|
|
102
102
|
name: "High-Impact Patterns",
|
|
103
103
|
impact: -Math.round(highImpactPenalty),
|
|
104
|
-
description: `${highImpactDuplicates} high-impact duplicates (
|
|
104
|
+
description: `${highImpactDuplicates} high-impact duplicates (Critical/Major or >1000 tokens)`
|
|
105
105
|
});
|
|
106
106
|
} else {
|
|
107
107
|
factors.push({
|