@aiready/pattern-detect 0.17.8 → 0.17.12
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 +1 -1
- package/dist/analyzer-entry/index.d.ts +1 -1
- package/dist/analyzer-entry/index.js +370 -135
- package/dist/analyzer-entry/index.mjs +4 -3
- package/dist/chunk-2P7BQHGR.mjs +306 -0
- package/dist/{chunk-VGMM3L3O.mjs → chunk-3EORD7DC.mjs} +1 -1
- package/dist/{chunk-GREN7X5H.mjs → chunk-4PVPQMRT.mjs} +2 -2
- package/dist/{chunk-RS73WLNI.mjs → chunk-6VDL7TAS.mjs} +5 -113
- package/dist/chunk-AQIP4JGM.mjs +283 -0
- package/dist/{chunk-JBUZ6YHE.mjs → chunk-B4NLWKPZ.mjs} +85 -9
- package/dist/chunk-IPBGVPUX.mjs +143 -0
- package/dist/chunk-LUUJOUK5.mjs +283 -0
- package/dist/chunk-P3BOCGVV.mjs +498 -0
- package/dist/{scoring-entry.js → chunk-PHJE6A3J.mjs} +20 -37
- package/dist/chunk-PQXOORR4.mjs +234 -0
- package/dist/{chunk-GLKAGFKX.mjs → chunk-RDR75DVI.mjs} +85 -9
- package/dist/chunk-SXVLRPMF.mjs +143 -0
- package/dist/{chunk-DNZS4ESD.mjs → chunk-SY7RX5YQ.mjs} +85 -9
- package/dist/{context-rules-entry.js → chunk-TIBF7KST.mjs} +81 -78
- package/dist/chunk-WYYSQX5M.mjs +467 -0
- package/dist/{chunk-I6ETJC7L.mjs → chunk-X553BOMI.mjs} +56 -26
- package/dist/{chunk-K7BO57OO.mjs → chunk-Y6OB7K34.mjs} +80 -4
- package/dist/chunk-YLVV6YZ5.mjs +143 -0
- package/dist/chunk-ZUWPFVJV.mjs +115 -0
- package/dist/chunk-ZZMONVPE.mjs +467 -0
- package/dist/cli.js +402 -167
- package/dist/cli.mjs +4 -3
- package/dist/context-rules-entry/index.d.mts +35 -1
- package/dist/context-rules-entry/index.d.ts +35 -1
- package/dist/context-rules-entry/index.js +194 -48
- package/dist/context-rules-entry/index.mjs +1 -1
- package/dist/detector-entry/index.js +192 -46
- package/dist/detector-entry/index.mjs +2 -2
- package/dist/{analyzer-entry-BVz-HnZd.d.mts → index-B-pnXpgn.d.mts} +10 -1
- package/dist/{index-BwuoiCNm.d.ts → index-CWgYOKaK.d.ts} +35 -16
- package/dist/{index-BVz-HnZd.d.mts → index-Dl4BrGIT.d.mts} +35 -16
- package/dist/{analyzer-entry-BwuoiCNm.d.ts → index-DqS2e0kK.d.ts} +10 -1
- package/dist/index.d.mts +5 -6
- package/dist/index.d.ts +5 -6
- package/dist/index.js +467 -214
- package/dist/index.mjs +37 -22
- package/dist/scoring-entry/index.js +7 -3
- package/dist/scoring-entry/index.mjs +1 -1
- package/package.json +2 -2
- package/dist/analyzer-entry.d.mts +0 -100
- package/dist/analyzer-entry.d.ts +0 -100
- package/dist/analyzer-entry.js +0 -693
- package/dist/analyzer-entry.mjs +0 -12
- package/dist/chunk-262N2JB7.mjs +0 -497
- package/dist/chunk-2R7HOR5H.mjs +0 -777
- package/dist/chunk-3D7RVGHM.mjs +0 -64
- package/dist/chunk-3LS3E6MO.mjs +0 -508
- package/dist/chunk-3VRQYFW3.mjs +0 -782
- package/dist/chunk-3WK24ZOX.mjs +0 -860
- package/dist/chunk-3YYN6ZXN.mjs +0 -1038
- package/dist/chunk-4BPRGZRG.mjs +0 -1041
- package/dist/chunk-4UHDGB7U.mjs +0 -920
- package/dist/chunk-5LYDB7DY.mjs +0 -771
- package/dist/chunk-65G3HXLQ.mjs +0 -497
- package/dist/chunk-65UQ5J2J.mjs +0 -64
- package/dist/chunk-6JTVOBJX.mjs +0 -64
- package/dist/chunk-6OEHUI5J.mjs +0 -1045
- package/dist/chunk-6YUGU4P4.mjs +0 -914
- package/dist/chunk-7EJGNGXM.mjs +0 -771
- package/dist/chunk-7O2DUBSN.mjs +0 -1058
- package/dist/chunk-7S4AUL5S.mjs +0 -911
- package/dist/chunk-A76JUWER.mjs +0 -786
- package/dist/chunk-AJZUNNFH.mjs +0 -817
- package/dist/chunk-AXHGYYYZ.mjs +0 -404
- package/dist/chunk-BKRPSTT2.mjs +0 -64
- package/dist/chunk-BUBQ3W6W.mjs +0 -980
- package/dist/chunk-CCHM2VLK.mjs +0 -1051
- package/dist/chunk-CHFK6EBT.mjs +0 -419
- package/dist/chunk-CMT3MWWO.mjs +0 -948
- package/dist/chunk-CMWW24HW.mjs +0 -259
- package/dist/chunk-CTDBJP25.mjs +0 -1043
- package/dist/chunk-DGAKXYIP.mjs +0 -1041
- package/dist/chunk-DQSLTL7J.mjs +0 -788
- package/dist/chunk-DR5W7S3Z.mjs +0 -968
- package/dist/chunk-EFUKPMBE.mjs +0 -950
- package/dist/chunk-EVBFDILL.mjs +0 -927
- package/dist/chunk-EXORBAXR.mjs +0 -887
- package/dist/chunk-EZT3NZGB.mjs +0 -1057
- package/dist/chunk-FWUKMJEQ.mjs +0 -1133
- package/dist/chunk-GSJFORRO.mjs +0 -504
- package/dist/chunk-H4ADJYOG.mjs +0 -925
- package/dist/chunk-H5FB2USZ.mjs +0 -762
- package/dist/chunk-H73HEG7M.mjs +0 -670
- package/dist/chunk-HOS5Z2NC.mjs +0 -669
- package/dist/chunk-HXHQOQB5.mjs +0 -508
- package/dist/chunk-INEOYHUM.mjs +0 -911
- package/dist/chunk-INJ4SBTV.mjs +0 -754
- package/dist/chunk-J5CW6NYY.mjs +0 -64
- package/dist/chunk-JAFZCZAP.mjs +0 -776
- package/dist/chunk-JKVKOXYR.mjs +0 -407
- package/dist/chunk-JTHW7EYW.mjs +0 -1041
- package/dist/chunk-JWR3AHKO.mjs +0 -788
- package/dist/chunk-KC2CQMG2.mjs +0 -858
- package/dist/chunk-KDWGWBP5.mjs +0 -832
- package/dist/chunk-KPEK5REL.mjs +0 -919
- package/dist/chunk-KT6O2IAE.mjs +0 -861
- package/dist/chunk-KWMNN3TG.mjs +0 -391
- package/dist/chunk-LUA5FXSZ.mjs +0 -771
- package/dist/chunk-LYKRYBSM.mjs +0 -64
- package/dist/chunk-M4PQMW34.mjs +0 -480
- package/dist/chunk-MH6LBXZF.mjs +0 -816
- package/dist/chunk-MHU3CL4R.mjs +0 -64
- package/dist/chunk-MJWBS6SM.mjs +0 -1058
- package/dist/chunk-OFGMDX66.mjs +0 -402
- package/dist/chunk-P7B6Z4I2.mjs +0 -1043
- package/dist/chunk-PBCXSG7E.mjs +0 -658
- package/dist/chunk-PEEHSFDR.mjs +0 -1058
- package/dist/chunk-PSVG2NLH.mjs +0 -966
- package/dist/chunk-PWNQ6JZW.mjs +0 -508
- package/dist/chunk-QE4E3F7C.mjs +0 -410
- package/dist/chunk-QEP76HGK.mjs +0 -1039
- package/dist/chunk-QX2BQJEO.mjs +0 -1058
- package/dist/chunk-R2S73CVG.mjs +0 -503
- package/dist/chunk-RMGDSNLE.mjs +0 -770
- package/dist/chunk-S2KQFII2.mjs +0 -491
- package/dist/chunk-SLDK5PQK.mjs +0 -1129
- package/dist/chunk-SNSDVGWW.mjs +0 -783
- package/dist/chunk-SUUZMLPS.mjs +0 -391
- package/dist/chunk-SVCSIZ2A.mjs +0 -259
- package/dist/chunk-T2C6WS73.mjs +0 -670
- package/dist/chunk-TCG2G32F.mjs +0 -911
- package/dist/chunk-TGBZP7SB.mjs +0 -773
- package/dist/chunk-THF4RW63.mjs +0 -254
- package/dist/chunk-TJKDLVLN.mjs +0 -503
- package/dist/chunk-TXWPOVYU.mjs +0 -402
- package/dist/chunk-UB3CGOQ7.mjs +0 -64
- package/dist/chunk-UKIKN27B.mjs +0 -950
- package/dist/chunk-V5DP4FP6.mjs +0 -876
- package/dist/chunk-VRMXVYDZ.mjs +0 -419
- package/dist/chunk-WACZ5LFH.mjs +0 -1055
- package/dist/chunk-WC7CBAA7.mjs +0 -1058
- package/dist/chunk-WKBCNITM.mjs +0 -1072
- package/dist/chunk-WMOGJFME.mjs +0 -391
- package/dist/chunk-X4GR2N2M.mjs +0 -947
- package/dist/chunk-XCWY2DQY.mjs +0 -788
- package/dist/chunk-XJD35DS6.mjs +0 -1058
- package/dist/chunk-XNPID6FU.mjs +0 -391
- package/dist/chunk-XUUVS54V.mjs +0 -776
- package/dist/chunk-YCGV65F5.mjs +0 -508
- package/dist/chunk-YJYDBFT3.mjs +0 -780
- package/dist/chunk-YP3HEDQW.mjs +0 -859
- package/dist/chunk-YSDOUNJJ.mjs +0 -1142
- package/dist/chunk-Z6GBFFOV.mjs +0 -1040
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/context-rules-entry-y2uJSngh.d.mts +0 -60
- package/dist/context-rules-entry-y2uJSngh.d.ts +0 -60
- package/dist/context-rules-entry.d.mts +0 -55
- package/dist/context-rules-entry.d.ts +0 -55
- package/dist/context-rules-entry.mjs +0 -12
- package/dist/context-rules.d.ts +0 -41
- package/dist/context-rules.d.ts.map +0 -1
- package/dist/context-rules.js +0 -225
- package/dist/context-rules.js.map +0 -1
- package/dist/detector-entry.d.mts +0 -14
- package/dist/detector-entry.d.ts +0 -14
- package/dist/detector-entry.js +0 -301
- package/dist/detector-entry.mjs +0 -7
- package/dist/detector.d.ts +0 -40
- package/dist/detector.d.ts.map +0 -1
- package/dist/detector.js +0 -385
- package/dist/detector.js.map +0 -1
- package/dist/extractors/python-extractor.d.ts +0 -19
- package/dist/extractors/python-extractor.d.ts.map +0 -1
- package/dist/extractors/python-extractor.js +0 -164
- package/dist/extractors/python-extractor.js.map +0 -1
- package/dist/grouping.d.ts +0 -54
- package/dist/grouping.d.ts.map +0 -1
- package/dist/grouping.js +0 -347
- package/dist/grouping.js.map +0 -1
- package/dist/index-y2uJSngh.d.mts +0 -60
- package/dist/index-y2uJSngh.d.ts +0 -60
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/python-extractor-BGKGX6BK.mjs +0 -131
- package/dist/python-extractor-ELAKYK2W.mjs +0 -140
- package/dist/scoring-entry.d.mts +0 -23
- package/dist/scoring-entry.d.ts +0 -23
- package/dist/scoring-entry.mjs +0 -6
- package/dist/scoring.d.ts +0 -12
- package/dist/scoring.d.ts.map +0 -1
- package/dist/scoring.js +0 -116
- package/dist/scoring.js.map +0 -1
- package/dist/types-C4lmb2Yh.d.mts +0 -36
- package/dist/types-C4lmb2Yh.d.ts +0 -36
package/dist/cli.js
CHANGED
|
@@ -27,14 +27,17 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
var import_commander = require("commander");
|
|
28
28
|
|
|
29
29
|
// src/analyzer.ts
|
|
30
|
-
var
|
|
30
|
+
var import_core11 = require("@aiready/core");
|
|
31
31
|
|
|
32
32
|
// src/detector.ts
|
|
33
|
-
var
|
|
33
|
+
var import_core6 = require("@aiready/core");
|
|
34
34
|
|
|
35
35
|
// src/context-rules.ts
|
|
36
|
+
var import_core5 = require("@aiready/core");
|
|
37
|
+
|
|
38
|
+
// src/rules/categories/test-rules.ts
|
|
36
39
|
var import_core = require("@aiready/core");
|
|
37
|
-
var
|
|
40
|
+
var TEST_RULES = [
|
|
38
41
|
// Test Fixtures - Intentional duplication for test isolation
|
|
39
42
|
{
|
|
40
43
|
name: "test-fixtures",
|
|
@@ -47,6 +50,35 @@ var CONTEXT_RULES = [
|
|
|
47
50
|
reason: "Test fixture duplication is intentional for test isolation",
|
|
48
51
|
suggestion: "Consider if shared test setup would improve maintainability without coupling tests"
|
|
49
52
|
},
|
|
53
|
+
// E2E/Integration Test Page Objects - Test independence
|
|
54
|
+
{
|
|
55
|
+
name: "e2e-page-objects",
|
|
56
|
+
detect: (file, code) => {
|
|
57
|
+
const isE2ETest = file.includes("e2e/") || file.includes("/e2e/") || file.includes(".e2e.") || file.includes("/playwright/") || file.includes("playwright/") || file.includes("/cypress/") || file.includes("cypress/") || file.includes("/integration/") || file.includes("integration/");
|
|
58
|
+
const hasPageObjectPatterns = code.includes("page.") || code.includes("await page") || code.includes("locator") || code.includes("getBy") || code.includes("selector") || code.includes("click(") || code.includes("fill(");
|
|
59
|
+
return isE2ETest && hasPageObjectPatterns;
|
|
60
|
+
},
|
|
61
|
+
severity: import_core.Severity.Info,
|
|
62
|
+
reason: "E2E test duplication ensures test independence and reduces coupling",
|
|
63
|
+
suggestion: "Consider page object pattern only if duplication causes maintenance issues"
|
|
64
|
+
},
|
|
65
|
+
// Mock Data - Test data intentionally duplicated
|
|
66
|
+
{
|
|
67
|
+
name: "mock-data",
|
|
68
|
+
detect: (file, code) => {
|
|
69
|
+
const isMockFile = file.includes("/mocks/") || file.includes("/__mocks__/") || file.includes("/fixtures/") || file.includes(".mock.") || file.includes(".fixture.");
|
|
70
|
+
const hasMockData = code.includes("mock") || code.includes("Mock") || code.includes("fixture") || code.includes("stub") || code.includes("export const");
|
|
71
|
+
return isMockFile && hasMockData;
|
|
72
|
+
},
|
|
73
|
+
severity: import_core.Severity.Info,
|
|
74
|
+
reason: "Mock data duplication is expected for comprehensive test coverage",
|
|
75
|
+
suggestion: "Consider shared factories only for complex mock generation"
|
|
76
|
+
}
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
// src/rules/categories/web-rules.ts
|
|
80
|
+
var import_core2 = require("@aiready/core");
|
|
81
|
+
var WEB_RULES = [
|
|
50
82
|
// Email/Document Templates - Often intentionally similar for consistency
|
|
51
83
|
{
|
|
52
84
|
name: "templates",
|
|
@@ -55,66 +87,59 @@ var CONTEXT_RULES = [
|
|
|
55
87
|
const hasTemplateContent = (code.includes("return") || code.includes("export")) && (code.includes("html") || code.includes("subject") || code.includes("body"));
|
|
56
88
|
return isTemplate && hasTemplateContent;
|
|
57
89
|
},
|
|
58
|
-
severity:
|
|
90
|
+
severity: import_core2.Severity.Info,
|
|
59
91
|
reason: "Template duplication may be intentional for maintainability and branding consistency",
|
|
60
92
|
suggestion: "Extract shared structure only if templates become hard to maintain"
|
|
61
93
|
},
|
|
62
|
-
//
|
|
94
|
+
// Common UI Event Handlers - Very specific patterns only
|
|
63
95
|
{
|
|
64
|
-
name: "
|
|
96
|
+
name: "common-ui-handlers",
|
|
65
97
|
detect: (file, code) => {
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
return
|
|
98
|
+
const isUIFile = file.includes("/components/") || file.includes(".tsx") || file.includes(".jsx") || file.includes("/hooks/");
|
|
99
|
+
const hasCommonHandler = code.includes("handleClickOutside") && code.includes("dropdownRef.current") && code.includes("!dropdownRef.current.contains") || code.includes("handleEscape") && code.includes("event.key") && code.includes("=== 'Escape'") || code.includes("handleClickInside") && code.includes("event.stopPropagation");
|
|
100
|
+
return isUIFile && hasCommonHandler;
|
|
69
101
|
},
|
|
70
|
-
severity:
|
|
71
|
-
reason: "
|
|
72
|
-
suggestion: "Consider
|
|
102
|
+
severity: import_core2.Severity.Info,
|
|
103
|
+
reason: "Common UI event handlers are boilerplate patterns that repeat across components",
|
|
104
|
+
suggestion: "Consider extracting to shared hooks (useClickOutside, useEscapeKey) only if causing maintenance issues"
|
|
73
105
|
},
|
|
106
|
+
// Next.js Route Handler Patterns - Boilerplate API route patterns
|
|
107
|
+
{
|
|
108
|
+
name: "nextjs-route-handlers",
|
|
109
|
+
detect: (file, code) => {
|
|
110
|
+
const isRouteFile = file.includes("/api/") && (file.endsWith("/route.ts") || file.endsWith("/route.js"));
|
|
111
|
+
const hasRoutePattern = code.includes("export async function POST") || code.includes("export async function GET") || code.includes("export async function PUT") || code.includes("export async function DELETE") || code.includes("NextResponse.json") || code.includes("NextRequest");
|
|
112
|
+
return isRouteFile && hasRoutePattern;
|
|
113
|
+
},
|
|
114
|
+
severity: import_core2.Severity.Info,
|
|
115
|
+
reason: "Next.js route handlers follow standard patterns and are intentionally similar across endpoints",
|
|
116
|
+
suggestion: "Route handler duplication is acceptable for API endpoint boilerplate"
|
|
117
|
+
}
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
// src/rules/categories/infra-rules.ts
|
|
121
|
+
var import_core3 = require("@aiready/core");
|
|
122
|
+
var INFRA_RULES = [
|
|
74
123
|
// Configuration Files - Often necessarily similar by design
|
|
75
124
|
{
|
|
76
125
|
name: "config-files",
|
|
77
126
|
detect: (file) => {
|
|
78
127
|
return file.endsWith(".config.ts") || file.endsWith(".config.js") || file.includes("jest.config") || file.includes("vite.config") || file.includes("webpack.config") || file.includes("rollup.config") || file.includes("tsconfig");
|
|
79
128
|
},
|
|
80
|
-
severity:
|
|
129
|
+
severity: import_core3.Severity.Info,
|
|
81
130
|
reason: "Configuration files often have similar structure by design",
|
|
82
131
|
suggestion: "Consider shared config base only if configurations become hard to maintain"
|
|
83
132
|
},
|
|
84
|
-
// Type Definitions - Duplication for type safety and module independence
|
|
85
|
-
{
|
|
86
|
-
name: "type-definitions",
|
|
87
|
-
detect: (file, code) => {
|
|
88
|
-
const isTypeFile = file.endsWith(".d.ts") || file.includes("/types/");
|
|
89
|
-
const hasTypeDefinitions = code.includes("interface ") || code.includes("type ") || code.includes("enum ");
|
|
90
|
-
return isTypeFile && hasTypeDefinitions;
|
|
91
|
-
},
|
|
92
|
-
severity: import_core.Severity.Info,
|
|
93
|
-
reason: "Type duplication may be intentional for module independence and type safety",
|
|
94
|
-
suggestion: "Extract to shared types package only if causing maintenance burden"
|
|
95
|
-
},
|
|
96
133
|
// Migration Scripts - One-off scripts that are similar by nature
|
|
97
134
|
{
|
|
98
135
|
name: "migration-scripts",
|
|
99
136
|
detect: (file) => {
|
|
100
137
|
return file.includes("/migrations/") || file.includes("/migrate/") || file.includes(".migration.");
|
|
101
138
|
},
|
|
102
|
-
severity:
|
|
139
|
+
severity: import_core3.Severity.Info,
|
|
103
140
|
reason: "Migration scripts are typically one-off and intentionally similar",
|
|
104
141
|
suggestion: "Duplication is acceptable for migration scripts"
|
|
105
142
|
},
|
|
106
|
-
// Mock Data - Test data intentionally duplicated
|
|
107
|
-
{
|
|
108
|
-
name: "mock-data",
|
|
109
|
-
detect: (file, code) => {
|
|
110
|
-
const isMockFile = file.includes("/mocks/") || file.includes("/__mocks__/") || file.includes("/fixtures/") || file.includes(".mock.") || file.includes(".fixture.");
|
|
111
|
-
const hasMockData = code.includes("mock") || code.includes("Mock") || code.includes("fixture") || code.includes("stub") || code.includes("export const");
|
|
112
|
-
return isMockFile && hasMockData;
|
|
113
|
-
},
|
|
114
|
-
severity: import_core.Severity.Info,
|
|
115
|
-
reason: "Mock data duplication is expected for comprehensive test coverage",
|
|
116
|
-
suggestion: "Consider shared factories only for complex mock generation"
|
|
117
|
-
},
|
|
118
143
|
// Tool Implementations - Structural Boilerplate
|
|
119
144
|
{
|
|
120
145
|
name: "tool-implementations",
|
|
@@ -123,11 +148,132 @@ var CONTEXT_RULES = [
|
|
|
123
148
|
const hasToolStructure = code.includes("execute") && (code.includes("try") || code.includes("catch"));
|
|
124
149
|
return isToolFile && hasToolStructure;
|
|
125
150
|
},
|
|
126
|
-
severity:
|
|
151
|
+
severity: import_core3.Severity.Info,
|
|
127
152
|
reason: "Tool implementations share structural boilerplate but have distinct business logic",
|
|
128
153
|
suggestion: "Tool duplication is acceptable for boilerplate interface wrappers"
|
|
154
|
+
},
|
|
155
|
+
// CLI Command Definitions - Commander.js boilerplate patterns
|
|
156
|
+
{
|
|
157
|
+
name: "cli-command-definitions",
|
|
158
|
+
detect: (file, code) => {
|
|
159
|
+
const isCliFile = file.includes("/commands/") || file.includes("/cli/") || file.endsWith(".command.ts");
|
|
160
|
+
const hasCommandPattern = (code.includes(".command(") || code.includes("defineCommand")) && (code.includes(".description(") || code.includes(".option(")) && (code.includes(".action(") || code.includes("async"));
|
|
161
|
+
return isCliFile && hasCommandPattern;
|
|
162
|
+
},
|
|
163
|
+
severity: import_core3.Severity.Info,
|
|
164
|
+
reason: "CLI command definitions follow standard Commander.js patterns and are intentionally similar",
|
|
165
|
+
suggestion: "Command boilerplate duplication is acceptable for CLI interfaces"
|
|
166
|
+
}
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
// src/rules/categories/logic-rules.ts
|
|
170
|
+
var import_core4 = require("@aiready/core");
|
|
171
|
+
var LOGIC_RULES = [
|
|
172
|
+
// Type Definitions - Duplication for type safety and module independence
|
|
173
|
+
{
|
|
174
|
+
name: "type-definitions",
|
|
175
|
+
detect: (file, code) => {
|
|
176
|
+
const isTypeFile = file.endsWith(".d.ts") || file.includes("/types/");
|
|
177
|
+
const hasOnlyTypeDefinitions = (code.includes("interface ") || code.includes("type ") || code.includes("enum ")) && !code.includes("function ") && !code.includes("class ") && !code.includes("const ") && !code.includes("let ") && !code.includes("export default");
|
|
178
|
+
const isInterfaceOnlySnippet = code.trim().startsWith("interface ") && code.includes("{") && code.includes("}") && !code.includes("function ") && !code.includes("const ") && !code.includes("return ");
|
|
179
|
+
return isTypeFile && hasOnlyTypeDefinitions || isInterfaceOnlySnippet;
|
|
180
|
+
},
|
|
181
|
+
severity: import_core4.Severity.Info,
|
|
182
|
+
reason: "Type/interface definitions are intentionally duplicated for module independence",
|
|
183
|
+
suggestion: "Extract to shared types package only if causing maintenance burden"
|
|
184
|
+
},
|
|
185
|
+
// Utility Functions - Small helpers in dedicated utility files
|
|
186
|
+
{
|
|
187
|
+
name: "utility-functions",
|
|
188
|
+
detect: (file, code) => {
|
|
189
|
+
const isUtilFile = file.endsWith(".util.ts") || file.endsWith(".helper.ts") || file.endsWith(".utils.ts");
|
|
190
|
+
const hasUtilPattern = code.includes("function format") || code.includes("function parse") || code.includes("function sanitize") || code.includes("function normalize") || code.includes("function convert");
|
|
191
|
+
return isUtilFile && hasUtilPattern;
|
|
192
|
+
},
|
|
193
|
+
severity: import_core4.Severity.Info,
|
|
194
|
+
reason: "Utility functions in dedicated utility files may be intentionally similar",
|
|
195
|
+
suggestion: "Consider extracting to shared utilities only if causing significant duplication"
|
|
196
|
+
},
|
|
197
|
+
// React/Vue Hooks - Standard patterns
|
|
198
|
+
{
|
|
199
|
+
name: "shared-hooks",
|
|
200
|
+
detect: (file, code) => {
|
|
201
|
+
const isHookFile = file.includes("/hooks/") || file.endsWith(".hook.ts") || file.endsWith(".hook.tsx");
|
|
202
|
+
const hasHookPattern = code.includes("function use") || code.includes("export function use") || code.includes("const use") || code.includes("export const use");
|
|
203
|
+
return isHookFile && hasHookPattern;
|
|
204
|
+
},
|
|
205
|
+
severity: import_core4.Severity.Info,
|
|
206
|
+
reason: "Hooks follow standard patterns and are often intentionally similar across components",
|
|
207
|
+
suggestion: "Consider extracting common hook logic only if hooks become complex"
|
|
208
|
+
},
|
|
209
|
+
// Score/Rating Helper Functions - Common threshold patterns
|
|
210
|
+
{
|
|
211
|
+
name: "score-helpers",
|
|
212
|
+
detect: (file, code) => {
|
|
213
|
+
const isHelperFile = file.includes("/utils/") || file.includes("/helpers/") || file.endsWith(".util.ts");
|
|
214
|
+
const hasScorePattern = (code.includes("if (score >=") || code.includes("if (score >")) && code.includes("return") && code.includes("'") && code.split("if (score").length >= 3;
|
|
215
|
+
return isHelperFile && hasScorePattern;
|
|
216
|
+
},
|
|
217
|
+
severity: import_core4.Severity.Info,
|
|
218
|
+
reason: "Score/rating helper functions use common threshold patterns that are intentionally similar",
|
|
219
|
+
suggestion: "Score formatting duplication is acceptable for consistent UI display"
|
|
220
|
+
},
|
|
221
|
+
// D3/Canvas Event Handlers - Standard visualization patterns
|
|
222
|
+
{
|
|
223
|
+
name: "visualization-handlers",
|
|
224
|
+
detect: (file, code) => {
|
|
225
|
+
const isVizFile = file.includes("/visualizer/") || file.includes("/charts/") || file.includes("GraphCanvas") || file.includes("ForceDirected");
|
|
226
|
+
const hasVizPattern = (code.includes("dragstarted") || code.includes("dragged") || code.includes("dragended")) && (code.includes("simulation") || code.includes("d3.") || code.includes("alphaTarget"));
|
|
227
|
+
return isVizFile && hasVizPattern;
|
|
228
|
+
},
|
|
229
|
+
severity: import_core4.Severity.Info,
|
|
230
|
+
reason: "D3/visualization event handlers follow standard patterns and are intentionally similar",
|
|
231
|
+
suggestion: "Visualization boilerplate duplication is acceptable for interactive charts"
|
|
232
|
+
},
|
|
233
|
+
// Icon/Switch Statement Helpers - Common enum-to-value patterns
|
|
234
|
+
{
|
|
235
|
+
name: "switch-helpers",
|
|
236
|
+
detect: (file, code) => {
|
|
237
|
+
const hasSwitchPattern = code.includes("switch (") && code.includes("case '") && code.includes("return") && code.split("case ").length >= 4;
|
|
238
|
+
const hasIconPattern = code.includes("getIcon") || code.includes("getColor") || code.includes("getLabel") || code.includes("getRating");
|
|
239
|
+
return hasSwitchPattern && hasIconPattern;
|
|
240
|
+
},
|
|
241
|
+
severity: import_core4.Severity.Info,
|
|
242
|
+
reason: "Switch statement helpers for enum-to-value mapping are inherently similar",
|
|
243
|
+
suggestion: "Switch duplication is acceptable for mapping enums to display values"
|
|
244
|
+
},
|
|
245
|
+
// Common API/Utility Functions - Legitimate duplication across modules
|
|
246
|
+
{
|
|
247
|
+
name: "common-api-functions",
|
|
248
|
+
detect: (file, code) => {
|
|
249
|
+
const isApiFile = file.includes("/api/") || file.includes("/lib/") || file.includes("/utils/") || file.endsWith(".ts");
|
|
250
|
+
const hasCommonApiPattern = code.includes("getStripe") && code.includes("process.env.STRIPE_SECRET_KEY") || code.includes("getUserByEmail") && code.includes("queryItems") || code.includes("updateUser") && code.includes("buildUpdateExpression") || code.includes("listUserRepositories") && code.includes("queryItems") || code.includes("listTeamRepositories") && code.includes("queryItems") || code.includes("getRemediation") && code.includes("queryItems") || code.includes("formatBreakdownKey") && code.includes(".replace(/([A-Z])/g") || code.includes("queryItems") && code.includes("KeyConditionExpression") || code.includes("putItem") && code.includes("createdAt") || code.includes("updateItem") && code.includes("buildUpdateExpression");
|
|
251
|
+
return isApiFile && hasCommonApiPattern;
|
|
252
|
+
},
|
|
253
|
+
severity: import_core4.Severity.Info,
|
|
254
|
+
reason: "Common API/utility functions are legitimately duplicated across modules for clarity and independence",
|
|
255
|
+
suggestion: "Consider extracting to shared utilities only if causing significant duplication"
|
|
256
|
+
},
|
|
257
|
+
// Validation Functions - Inherently similar patterns
|
|
258
|
+
{
|
|
259
|
+
name: "validation-functions",
|
|
260
|
+
detect: (file, code) => {
|
|
261
|
+
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");
|
|
262
|
+
return hasValidationPattern;
|
|
263
|
+
},
|
|
264
|
+
severity: import_core4.Severity.Info,
|
|
265
|
+
reason: "Validation functions are inherently similar and often intentionally duplicated for domain clarity",
|
|
266
|
+
suggestion: "Consider extracting to shared validators only if validation logic becomes complex"
|
|
129
267
|
}
|
|
130
268
|
];
|
|
269
|
+
|
|
270
|
+
// src/context-rules.ts
|
|
271
|
+
var CONTEXT_RULES = [
|
|
272
|
+
...TEST_RULES,
|
|
273
|
+
...WEB_RULES,
|
|
274
|
+
...INFRA_RULES,
|
|
275
|
+
...LOGIC_RULES
|
|
276
|
+
];
|
|
131
277
|
function calculateSeverity(file1, file2, code, similarity, linesOfCode) {
|
|
132
278
|
for (const rule of CONTEXT_RULES) {
|
|
133
279
|
if (rule.detect(file1, code) || rule.detect(file2, code)) {
|
|
@@ -141,31 +287,31 @@ function calculateSeverity(file1, file2, code, similarity, linesOfCode) {
|
|
|
141
287
|
}
|
|
142
288
|
if (similarity >= 0.95 && linesOfCode >= 30) {
|
|
143
289
|
return {
|
|
144
|
-
severity:
|
|
290
|
+
severity: import_core5.Severity.Critical,
|
|
145
291
|
reason: "Large nearly-identical code blocks waste tokens and create maintenance burden",
|
|
146
292
|
suggestion: "Extract to shared utility module immediately"
|
|
147
293
|
};
|
|
148
294
|
} else if (similarity >= 0.95 && linesOfCode >= 15) {
|
|
149
295
|
return {
|
|
150
|
-
severity:
|
|
296
|
+
severity: import_core5.Severity.Major,
|
|
151
297
|
reason: "Nearly identical code should be consolidated",
|
|
152
298
|
suggestion: "Move to shared utility file"
|
|
153
299
|
};
|
|
154
300
|
} else if (similarity >= 0.85) {
|
|
155
301
|
return {
|
|
156
|
-
severity:
|
|
302
|
+
severity: import_core5.Severity.Major,
|
|
157
303
|
reason: "High similarity indicates significant duplication",
|
|
158
304
|
suggestion: "Extract common logic to shared function"
|
|
159
305
|
};
|
|
160
306
|
} else if (similarity >= 0.7) {
|
|
161
307
|
return {
|
|
162
|
-
severity:
|
|
308
|
+
severity: import_core5.Severity.Minor,
|
|
163
309
|
reason: "Moderate similarity detected",
|
|
164
310
|
suggestion: "Consider extracting shared patterns if code evolves together"
|
|
165
311
|
};
|
|
166
312
|
} else {
|
|
167
313
|
return {
|
|
168
|
-
severity:
|
|
314
|
+
severity: import_core5.Severity.Minor,
|
|
169
315
|
reason: "Minor similarity detected",
|
|
170
316
|
suggestion: "Monitor but refactoring may not be worthwhile"
|
|
171
317
|
};
|
|
@@ -186,13 +332,13 @@ function normalizeCode(code, isPython = false) {
|
|
|
186
332
|
|
|
187
333
|
// src/detector.ts
|
|
188
334
|
function extractBlocks(file, content) {
|
|
189
|
-
return (0,
|
|
335
|
+
return (0, import_core6.extractCodeBlocks)(file, content);
|
|
190
336
|
}
|
|
191
337
|
function calculateSimilarity(a, b) {
|
|
192
|
-
return (0,
|
|
338
|
+
return (0, import_core6.calculateStringSimilarity)(a, b);
|
|
193
339
|
}
|
|
194
340
|
function calculateConfidence(similarity, tokens, lines) {
|
|
195
|
-
return (0,
|
|
341
|
+
return (0, import_core6.calculateHeuristicConfidence)(similarity, tokens, lines);
|
|
196
342
|
}
|
|
197
343
|
async function detectDuplicatePatterns(fileContents, options) {
|
|
198
344
|
const {
|
|
@@ -302,7 +448,7 @@ async function detectDuplicatePatterns(fileContents, options) {
|
|
|
302
448
|
}
|
|
303
449
|
|
|
304
450
|
// src/grouping.ts
|
|
305
|
-
var
|
|
451
|
+
var import_core7 = require("@aiready/core");
|
|
306
452
|
var import_path = __toESM(require("path"));
|
|
307
453
|
function groupDuplicatesByFilePair(duplicates) {
|
|
308
454
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -330,7 +476,7 @@ function groupDuplicatesByFilePair(duplicates) {
|
|
|
330
476
|
file2: { start: dup.line2, end: dup.endLine2 }
|
|
331
477
|
});
|
|
332
478
|
const currentSev = dup.severity;
|
|
333
|
-
if ((0,
|
|
479
|
+
if ((0, import_core7.getSeverityLevel)(currentSev) > (0, import_core7.getSeverityLevel)(group.severity)) {
|
|
334
480
|
group.severity = currentSev;
|
|
335
481
|
}
|
|
336
482
|
}
|
|
@@ -420,21 +566,77 @@ function filterClustersByImpact(clusters, minTokenCost = 1e3, minFiles = 3) {
|
|
|
420
566
|
(c) => c.totalTokenCost >= minTokenCost && c.files.length >= minFiles
|
|
421
567
|
);
|
|
422
568
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
569
|
+
function isPureInterfaceDefinition(code) {
|
|
570
|
+
const trimmed = code.trim();
|
|
571
|
+
if (!trimmed.startsWith("interface ") && !trimmed.startsWith("type ") && !trimmed.startsWith("export interface ") && !trimmed.startsWith("export type ") && !trimmed.startsWith("enum ") && !trimmed.startsWith("export enum ")) {
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
if (trimmed.includes("={") || trimmed.includes("=> {") || trimmed.includes("function ") || trimmed.includes("() {") || trimmed.includes(" implements ")) {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
if (trimmed.length > 200) return false;
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
var BRAND_INDICATORS = [
|
|
581
|
+
"cyberpunk",
|
|
582
|
+
"cyber-blue",
|
|
583
|
+
"cyber-purple",
|
|
584
|
+
"slate-900",
|
|
585
|
+
"slate-400",
|
|
586
|
+
"zinc-",
|
|
587
|
+
"indigo-",
|
|
588
|
+
"neon-",
|
|
589
|
+
"glassmorphism",
|
|
590
|
+
"backdrop-blur"
|
|
591
|
+
];
|
|
592
|
+
function isBrandSpecificComponent(filePath) {
|
|
593
|
+
const lower = filePath.toLowerCase();
|
|
594
|
+
const brandingTerms = ["landing", "clawmore", "platform", "apps/"];
|
|
595
|
+
for (const term of brandingTerms) {
|
|
596
|
+
if (lower.includes(term)) return true;
|
|
597
|
+
}
|
|
598
|
+
return false;
|
|
437
599
|
}
|
|
600
|
+
function areBrandSpecificVariants(file1, file2, code1, code2) {
|
|
601
|
+
const f1IsBrand = isBrandSpecificComponent(file1);
|
|
602
|
+
const f2IsBrand = isBrandSpecificComponent(file2);
|
|
603
|
+
if (f1IsBrand && f2IsBrand && file1 !== file2) {
|
|
604
|
+
const hasBrandKeyword = (code) => {
|
|
605
|
+
const lowerCode = code.toLowerCase();
|
|
606
|
+
return BRAND_INDICATORS.some((ind) => lowerCode.includes(ind));
|
|
607
|
+
};
|
|
608
|
+
const code1Brand = hasBrandKeyword(code1);
|
|
609
|
+
const code2Brand = hasBrandKeyword(code2);
|
|
610
|
+
if (code1Brand && code2Brand) {
|
|
611
|
+
return true;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
function filterBrandSpecificVariants(duplicates) {
|
|
617
|
+
return duplicates.filter((dup) => {
|
|
618
|
+
if (dup.file1 === dup.file2) return true;
|
|
619
|
+
const isBrandVariant = areBrandSpecificVariants(
|
|
620
|
+
dup.file1,
|
|
621
|
+
dup.file2,
|
|
622
|
+
dup.code1,
|
|
623
|
+
dup.code2
|
|
624
|
+
);
|
|
625
|
+
if (isBrandVariant) {
|
|
626
|
+
dup.severity = import_core7.Severity.Info;
|
|
627
|
+
dup.suggestion = "Brand-specific themed component variant (intentional)";
|
|
628
|
+
}
|
|
629
|
+
const isInterfaceDef = isPureInterfaceDefinition(dup.code1) && isPureInterfaceDefinition(dup.code2);
|
|
630
|
+
if (isInterfaceDef) {
|
|
631
|
+
dup.severity = import_core7.Severity.Info;
|
|
632
|
+
dup.suggestion = "Pure interface/type definition - intentional for module independence";
|
|
633
|
+
}
|
|
634
|
+
return true;
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/config.ts
|
|
639
|
+
var import_core8 = require("@aiready/core");
|
|
438
640
|
async function getSmartDefaults(directory, userOptions) {
|
|
439
641
|
if (userOptions.useSmartDefaults === false) {
|
|
440
642
|
return {
|
|
@@ -455,7 +657,7 @@ async function getSmartDefaults(directory, userOptions) {
|
|
|
455
657
|
include: userOptions.include || ["**/*.{ts,tsx,js,jsx,py,java}"],
|
|
456
658
|
exclude: userOptions.exclude
|
|
457
659
|
};
|
|
458
|
-
const files = await (0,
|
|
660
|
+
const files = await (0, import_core8.scanFiles)(scanOptions);
|
|
459
661
|
const fileCount = files.length;
|
|
460
662
|
const estimatedBlocks = fileCount * 5;
|
|
461
663
|
const minLines = Math.max(
|
|
@@ -513,6 +715,98 @@ function logConfiguration(config, estimatedBlocks) {
|
|
|
513
715
|
}
|
|
514
716
|
console.log("");
|
|
515
717
|
}
|
|
718
|
+
|
|
719
|
+
// src/summary.ts
|
|
720
|
+
var import_core9 = require("@aiready/core");
|
|
721
|
+
function getRefactoringSuggestion(patternType, similarity) {
|
|
722
|
+
const baseMessages = {
|
|
723
|
+
"api-handler": "Extract common middleware or create a base handler class",
|
|
724
|
+
validator: "Consolidate validation logic into shared schema validators (Zod/Yup)",
|
|
725
|
+
utility: "Move to a shared utilities file and reuse across modules",
|
|
726
|
+
"class-method": "Consider inheritance or composition to share behavior",
|
|
727
|
+
component: "Extract shared logic into a custom hook or HOC",
|
|
728
|
+
function: "Extract into a shared helper function",
|
|
729
|
+
unknown: "Extract common logic into a reusable module"
|
|
730
|
+
};
|
|
731
|
+
const urgency = similarity > 0.95 ? " (CRITICAL: Nearly identical code)" : similarity > 0.9 ? " (HIGH: Very similar, refactor soon)" : "";
|
|
732
|
+
return baseMessages[patternType] + urgency;
|
|
733
|
+
}
|
|
734
|
+
function generateSummary(results) {
|
|
735
|
+
const allIssues = results.flatMap((r) => r.issues || []);
|
|
736
|
+
const totalTokenCost = results.reduce(
|
|
737
|
+
(sum, r) => sum + (r.metrics?.tokenCost || 0),
|
|
738
|
+
0
|
|
739
|
+
);
|
|
740
|
+
const patternsByType = {
|
|
741
|
+
"api-handler": 0,
|
|
742
|
+
validator: 0,
|
|
743
|
+
utility: 0,
|
|
744
|
+
"class-method": 0,
|
|
745
|
+
component: 0,
|
|
746
|
+
function: 0,
|
|
747
|
+
unknown: 0
|
|
748
|
+
};
|
|
749
|
+
allIssues.forEach((issue) => {
|
|
750
|
+
const match = issue.message.match(/^(\S+(?:-\S+)*) pattern/);
|
|
751
|
+
if (match) {
|
|
752
|
+
const type = match[1] || "unknown";
|
|
753
|
+
patternsByType[type] = (patternsByType[type] || 0) + 1;
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
const topDuplicates = allIssues.slice(0, 10).map((issue) => {
|
|
757
|
+
const similarityMatch = issue.message.match(/(\d+)% similar/);
|
|
758
|
+
const tokenMatch = issue.message.match(/\((\d+) tokens/);
|
|
759
|
+
const typeMatch = issue.message.match(/^(\S+(?:-\S+)*) pattern/);
|
|
760
|
+
const fileMatch = issue.message.match(/similar to (.+?) \(/);
|
|
761
|
+
return {
|
|
762
|
+
files: [
|
|
763
|
+
{
|
|
764
|
+
path: issue.location.file,
|
|
765
|
+
startLine: issue.location.line,
|
|
766
|
+
endLine: 0
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
path: fileMatch?.[1] || "unknown",
|
|
770
|
+
startLine: 0,
|
|
771
|
+
endLine: 0
|
|
772
|
+
}
|
|
773
|
+
],
|
|
774
|
+
similarity: similarityMatch ? parseInt(similarityMatch[1]) / 100 : 0,
|
|
775
|
+
patternType: typeMatch?.[1] || "unknown",
|
|
776
|
+
tokenCost: tokenMatch ? parseInt(tokenMatch[1]) : 0
|
|
777
|
+
};
|
|
778
|
+
});
|
|
779
|
+
return {
|
|
780
|
+
totalPatterns: allIssues.length,
|
|
781
|
+
totalTokenCost,
|
|
782
|
+
patternsByType,
|
|
783
|
+
topDuplicates
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
function filterBySeverity2(issues, severity) {
|
|
787
|
+
if (severity === "all") return issues;
|
|
788
|
+
const severityMap = {
|
|
789
|
+
critical: [import_core9.Severity.Critical],
|
|
790
|
+
high: [import_core9.Severity.Critical, import_core9.Severity.Major],
|
|
791
|
+
medium: [import_core9.Severity.Critical, import_core9.Severity.Major, import_core9.Severity.Minor]
|
|
792
|
+
};
|
|
793
|
+
const allowed = severityMap[severity] || [
|
|
794
|
+
import_core9.Severity.Critical,
|
|
795
|
+
import_core9.Severity.Major,
|
|
796
|
+
import_core9.Severity.Minor
|
|
797
|
+
];
|
|
798
|
+
return issues.filter((issue) => allowed.includes(issue.severity));
|
|
799
|
+
}
|
|
800
|
+
function calculateSeverity2(similarity) {
|
|
801
|
+
if (similarity > 0.95) return import_core9.Severity.Critical;
|
|
802
|
+
if (similarity > 0.9) return import_core9.Severity.Major;
|
|
803
|
+
return import_core9.Severity.Minor;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// src/scoring.ts
|
|
807
|
+
var import_core10 = require("@aiready/core");
|
|
808
|
+
|
|
809
|
+
// src/analyzer.ts
|
|
516
810
|
async function analyzePatterns(options) {
|
|
517
811
|
const smartDefaults = await getSmartDefaults(options.rootDir || ".", options);
|
|
518
812
|
const finalOptions = { ...smartDefaults, ...options };
|
|
@@ -534,7 +828,7 @@ async function analyzePatterns(options) {
|
|
|
534
828
|
ignoreWhitelist = [],
|
|
535
829
|
...scanOptions
|
|
536
830
|
} = finalOptions;
|
|
537
|
-
const files = await (0,
|
|
831
|
+
const files = await (0, import_core11.scanFiles)(scanOptions);
|
|
538
832
|
const estimatedBlocks = files.length * 3;
|
|
539
833
|
logConfiguration(finalOptions, estimatedBlocks);
|
|
540
834
|
const results = [];
|
|
@@ -545,7 +839,7 @@ async function analyzePatterns(options) {
|
|
|
545
839
|
const batchContents = await Promise.all(
|
|
546
840
|
batch.map(async (file) => ({
|
|
547
841
|
file,
|
|
548
|
-
content: await (0,
|
|
842
|
+
content: await (0, import_core11.readFileContent)(file)
|
|
549
843
|
}))
|
|
550
844
|
);
|
|
551
845
|
fileContents.push(...batchContents);
|
|
@@ -563,16 +857,22 @@ async function analyzePatterns(options) {
|
|
|
563
857
|
ignoreWhitelist,
|
|
564
858
|
onProgress: options.onProgress
|
|
565
859
|
});
|
|
860
|
+
filterBrandSpecificVariants(duplicates);
|
|
566
861
|
for (const file of files) {
|
|
567
862
|
const fileDuplicates = duplicates.filter(
|
|
568
863
|
(dup) => dup.file1 === file || dup.file2 === file
|
|
569
864
|
);
|
|
570
865
|
const issues = fileDuplicates.map((dup) => {
|
|
571
866
|
const otherFile = dup.file1 === file ? dup.file2 : dup.file1;
|
|
572
|
-
|
|
867
|
+
let severityLevel;
|
|
868
|
+
if (dup.severity === "info" || dup.severity === "Info") {
|
|
869
|
+
severityLevel = import_core11.Severity.Info;
|
|
870
|
+
} else {
|
|
871
|
+
severityLevel = calculateSeverity2(dup.similarity);
|
|
872
|
+
}
|
|
573
873
|
return {
|
|
574
|
-
type:
|
|
575
|
-
severity:
|
|
874
|
+
type: import_core11.IssueType.DuplicatePattern,
|
|
875
|
+
severity: severityLevel,
|
|
576
876
|
message: `${dup.patternType} pattern ${Math.round(dup.similarity * 100)}% similar to ${otherFile} (${dup.tokenCost} tokens wasted)`,
|
|
577
877
|
location: {
|
|
578
878
|
file,
|
|
@@ -581,18 +881,7 @@ async function analyzePatterns(options) {
|
|
|
581
881
|
suggestion: getRefactoringSuggestion(dup.patternType, dup.similarity)
|
|
582
882
|
};
|
|
583
883
|
});
|
|
584
|
-
|
|
585
|
-
if (severity !== "all") {
|
|
586
|
-
const severityMap = {
|
|
587
|
-
critical: [import_core4.Severity.Critical],
|
|
588
|
-
high: [import_core4.Severity.Critical, import_core4.Severity.Major],
|
|
589
|
-
medium: [import_core4.Severity.Critical, import_core4.Severity.Major, import_core4.Severity.Minor]
|
|
590
|
-
};
|
|
591
|
-
const allowedSeverities = severityMap[severity] || [import_core4.Severity.Critical, import_core4.Severity.Major, import_core4.Severity.Minor];
|
|
592
|
-
filteredIssues = issues.filter(
|
|
593
|
-
(issue) => allowedSeverities.includes(issue.severity)
|
|
594
|
-
);
|
|
595
|
-
}
|
|
884
|
+
const filteredIssues = filterBySeverity2(issues, severity || "all");
|
|
596
885
|
const totalTokenCost = fileDuplicates.reduce(
|
|
597
886
|
(sum, dup) => sum + dup.tokenCost,
|
|
598
887
|
0
|
|
@@ -621,69 +910,15 @@ async function analyzePatterns(options) {
|
|
|
621
910
|
}
|
|
622
911
|
return { results, duplicates, files, groups, clusters, config: finalOptions };
|
|
623
912
|
}
|
|
624
|
-
function generateSummary(results) {
|
|
625
|
-
const allIssues = results.flatMap((r) => r.issues || []);
|
|
626
|
-
const totalTokenCost = results.reduce(
|
|
627
|
-
(sum, r) => sum + (r.metrics?.tokenCost || 0),
|
|
628
|
-
0
|
|
629
|
-
);
|
|
630
|
-
const patternsByType = {
|
|
631
|
-
"api-handler": 0,
|
|
632
|
-
validator: 0,
|
|
633
|
-
utility: 0,
|
|
634
|
-
"class-method": 0,
|
|
635
|
-
component: 0,
|
|
636
|
-
function: 0,
|
|
637
|
-
unknown: 0
|
|
638
|
-
};
|
|
639
|
-
allIssues.forEach((issue) => {
|
|
640
|
-
const match = issue.message.match(/^(\S+(?:-\S+)*) pattern/);
|
|
641
|
-
if (match) {
|
|
642
|
-
const type = match[1];
|
|
643
|
-
patternsByType[type] = (patternsByType[type] || 0) + 1;
|
|
644
|
-
}
|
|
645
|
-
});
|
|
646
|
-
const topDuplicates = allIssues.slice(0, 10).map((issue) => {
|
|
647
|
-
const similarityMatch = issue.message.match(/(\d+)% similar/);
|
|
648
|
-
const tokenMatch = issue.message.match(/\((\d+) tokens/);
|
|
649
|
-
const typeMatch = issue.message.match(/^(\S+(?:-\S+)*) pattern/);
|
|
650
|
-
const fileMatch = issue.message.match(/similar to (.+?) \(/);
|
|
651
|
-
return {
|
|
652
|
-
files: [
|
|
653
|
-
{
|
|
654
|
-
path: issue.location.file,
|
|
655
|
-
startLine: issue.location.line,
|
|
656
|
-
endLine: 0
|
|
657
|
-
},
|
|
658
|
-
{
|
|
659
|
-
path: fileMatch?.[1] || "unknown",
|
|
660
|
-
startLine: 0,
|
|
661
|
-
endLine: 0
|
|
662
|
-
}
|
|
663
|
-
],
|
|
664
|
-
similarity: similarityMatch ? parseInt(similarityMatch[1]) / 100 : 0,
|
|
665
|
-
confidence: similarityMatch ? parseInt(similarityMatch[1]) / 100 : 0,
|
|
666
|
-
// Fallback for summary
|
|
667
|
-
patternType: typeMatch?.[1] || "unknown",
|
|
668
|
-
tokenCost: tokenMatch ? parseInt(tokenMatch[1]) : 0
|
|
669
|
-
};
|
|
670
|
-
});
|
|
671
|
-
return {
|
|
672
|
-
totalPatterns: allIssues.length,
|
|
673
|
-
totalTokenCost,
|
|
674
|
-
patternsByType,
|
|
675
|
-
topDuplicates
|
|
676
|
-
};
|
|
677
|
-
}
|
|
678
913
|
|
|
679
914
|
// src/cli-action.ts
|
|
680
915
|
var import_chalk2 = __toESM(require("chalk"));
|
|
681
916
|
var import_fs = require("fs");
|
|
682
917
|
var import_path2 = require("path");
|
|
683
|
-
var
|
|
918
|
+
var import_core15 = require("@aiready/core");
|
|
684
919
|
|
|
685
920
|
// src/config-resolver.ts
|
|
686
|
-
var
|
|
921
|
+
var import_core12 = require("@aiready/core");
|
|
687
922
|
|
|
688
923
|
// src/constants.ts
|
|
689
924
|
var DEFAULT_MIN_SIMILARITY = 0.4;
|
|
@@ -715,7 +950,7 @@ EXAMPLES:
|
|
|
715
950
|
|
|
716
951
|
// src/config-resolver.ts
|
|
717
952
|
async function resolvePatternConfig(directory, options) {
|
|
718
|
-
const fileConfig = await (0,
|
|
953
|
+
const fileConfig = await (0, import_core12.loadConfig)(directory);
|
|
719
954
|
const defaults = {
|
|
720
955
|
minSimilarity: DEFAULT_MIN_SIMILARITY,
|
|
721
956
|
minLines: DEFAULT_MIN_LINES,
|
|
@@ -729,7 +964,7 @@ async function resolvePatternConfig(directory, options) {
|
|
|
729
964
|
excludePatterns: void 0,
|
|
730
965
|
confidenceThreshold: 0,
|
|
731
966
|
ignoreWhitelist: void 0,
|
|
732
|
-
minSeverity:
|
|
967
|
+
minSeverity: import_core12.Severity.Minor,
|
|
733
968
|
excludeTestFixtures: false,
|
|
734
969
|
excludeTemplates: false,
|
|
735
970
|
includeTests: false,
|
|
@@ -740,7 +975,7 @@ async function resolvePatternConfig(directory, options) {
|
|
|
740
975
|
minClusterFiles: DEFAULT_MIN_CLUSTER_FILES,
|
|
741
976
|
showRawDuplicates: false
|
|
742
977
|
};
|
|
743
|
-
const mergedConfig = (0,
|
|
978
|
+
const mergedConfig = (0, import_core12.mergeConfigWithDefaults)(fileConfig, defaults);
|
|
744
979
|
const finalOptions = {
|
|
745
980
|
rootDir: directory,
|
|
746
981
|
minSimilarity: options.similarity ? parseFloat(options.similarity) : mergedConfig.minSimilarity,
|
|
@@ -782,7 +1017,7 @@ async function resolvePatternConfig(directory, options) {
|
|
|
782
1017
|
}
|
|
783
1018
|
|
|
784
1019
|
// src/cli-output.ts
|
|
785
|
-
var
|
|
1020
|
+
var import_core13 = require("@aiready/core");
|
|
786
1021
|
function getPatternIcon(type) {
|
|
787
1022
|
const icons = {
|
|
788
1023
|
"api-handler": "\u{1F50C}",
|
|
@@ -808,7 +1043,7 @@ function generateHTMLReport(results, summary) {
|
|
|
808
1043
|
dup.files.map((f) => `<code>${f.path}:${f.startLine}-${f.endLine}</code>`).join("<br>\u2194<br>"),
|
|
809
1044
|
dup.tokenCost.toLocaleString()
|
|
810
1045
|
]);
|
|
811
|
-
return (0,
|
|
1046
|
+
return (0, import_core13.generateStandardHtmlReport)(
|
|
812
1047
|
{
|
|
813
1048
|
title: "Pattern Detection Report",
|
|
814
1049
|
packageName: "pattern-detect",
|
|
@@ -825,7 +1060,7 @@ function generateHTMLReport(results, summary) {
|
|
|
825
1060
|
[
|
|
826
1061
|
{
|
|
827
1062
|
title: "Duplicate Patterns",
|
|
828
|
-
content: (0,
|
|
1063
|
+
content: (0, import_core13.generateTable)({
|
|
829
1064
|
headers: ["Similarity", "Type", "Locations", "Tokens Wasted"],
|
|
830
1065
|
rows: tableRows
|
|
831
1066
|
})
|
|
@@ -837,9 +1072,9 @@ function generateHTMLReport(results, summary) {
|
|
|
837
1072
|
|
|
838
1073
|
// src/terminal-output.ts
|
|
839
1074
|
var import_chalk = __toESM(require("chalk"));
|
|
840
|
-
var
|
|
1075
|
+
var import_core14 = require("@aiready/core");
|
|
841
1076
|
function printAnalysisSummary(resultsLength, totalIssues, totalTokenCost, elapsedTime) {
|
|
842
|
-
(0,
|
|
1077
|
+
(0, import_core14.printTerminalHeader)("PATTERN ANALYSIS SUMMARY");
|
|
843
1078
|
console.log(import_chalk.default.white(`\u{1F4C1} Files analyzed: ${import_chalk.default.bold(resultsLength)}`));
|
|
844
1079
|
console.log(
|
|
845
1080
|
import_chalk.default.yellow(
|
|
@@ -856,9 +1091,9 @@ function printAnalysisSummary(resultsLength, totalIssues, totalTokenCost, elapse
|
|
|
856
1091
|
function printPatternBreakdown(patternsByType) {
|
|
857
1092
|
const sortedTypes = Object.entries(patternsByType).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a);
|
|
858
1093
|
if (sortedTypes.length > 0) {
|
|
859
|
-
console.log("\n" + (0,
|
|
1094
|
+
console.log("\n" + (0, import_core14.getTerminalDivider)());
|
|
860
1095
|
console.log(import_chalk.default.bold.white(" PATTERNS BY TYPE"));
|
|
861
|
-
console.log((0,
|
|
1096
|
+
console.log((0, import_core14.getTerminalDivider)() + "\n");
|
|
862
1097
|
sortedTypes.forEach(([type, count]) => {
|
|
863
1098
|
const icon = getPatternIcon(type);
|
|
864
1099
|
console.log(
|
|
@@ -869,20 +1104,20 @@ function printPatternBreakdown(patternsByType) {
|
|
|
869
1104
|
}
|
|
870
1105
|
function printDuplicateGroups(groups, maxResults) {
|
|
871
1106
|
if (groups.length === 0) return;
|
|
872
|
-
console.log("\n" + (0,
|
|
1107
|
+
console.log("\n" + (0, import_core14.getTerminalDivider)());
|
|
873
1108
|
console.log(
|
|
874
1109
|
import_chalk.default.bold.white(` \u{1F4E6} DUPLICATE GROUPS (${groups.length} file pairs)`)
|
|
875
1110
|
);
|
|
876
|
-
console.log((0,
|
|
1111
|
+
console.log((0, import_core14.getTerminalDivider)() + "\n");
|
|
877
1112
|
const topGroups = groups.sort((a, b) => {
|
|
878
|
-
const bVal = (0,
|
|
879
|
-
const aVal = (0,
|
|
1113
|
+
const bVal = (0, import_core14.getSeverityValue)(b.severity);
|
|
1114
|
+
const aVal = (0, import_core14.getSeverityValue)(a.severity);
|
|
880
1115
|
const severityDiff = bVal - aVal;
|
|
881
1116
|
if (severityDiff !== 0) return severityDiff;
|
|
882
1117
|
return b.totalTokenCost - a.totalTokenCost;
|
|
883
1118
|
}).slice(0, maxResults);
|
|
884
1119
|
topGroups.forEach((group, idx) => {
|
|
885
|
-
const severityBadge = (0,
|
|
1120
|
+
const severityBadge = (0, import_core14.getSeverityBadge)(group.severity);
|
|
886
1121
|
const [file1, file2] = group.filePair.split("::");
|
|
887
1122
|
const file1Name = file1.split("/").pop() || file1;
|
|
888
1123
|
const file2Name = file2.split("/").pop() || file2;
|
|
@@ -915,13 +1150,13 @@ function printDuplicateGroups(groups, maxResults) {
|
|
|
915
1150
|
}
|
|
916
1151
|
function printRefactorClusters(clusters) {
|
|
917
1152
|
if (clusters.length === 0) return;
|
|
918
|
-
console.log("\n" + (0,
|
|
1153
|
+
console.log("\n" + (0, import_core14.getTerminalDivider)());
|
|
919
1154
|
console.log(
|
|
920
1155
|
import_chalk.default.bold.white(` \u{1F3AF} REFACTOR CLUSTERS (${clusters.length} patterns)`)
|
|
921
1156
|
);
|
|
922
|
-
console.log((0,
|
|
1157
|
+
console.log((0, import_core14.getTerminalDivider)() + "\n");
|
|
923
1158
|
clusters.sort((a, b) => b.totalTokenCost - a.totalTokenCost).forEach((cluster, idx) => {
|
|
924
|
-
const severityBadge = (0,
|
|
1159
|
+
const severityBadge = (0, import_core14.getSeverityBadge)(cluster.severity);
|
|
925
1160
|
console.log(`${idx + 1}. ${severityBadge} ${import_chalk.default.bold(cluster.name)}`);
|
|
926
1161
|
console.log(
|
|
927
1162
|
` Total tokens: ${import_chalk.default.bold(cluster.totalTokenCost.toLocaleString())} | Avg similarity: ${import_chalk.default.bold(Math.round(cluster.averageSimilarity * 100) + "%")} | Duplicates: ${import_chalk.default.bold(cluster.duplicateCount)}`
|
|
@@ -948,18 +1183,18 @@ function printRefactorClusters(clusters) {
|
|
|
948
1183
|
}
|
|
949
1184
|
function printRawDuplicates(duplicates, maxResults) {
|
|
950
1185
|
if (duplicates.length === 0) return;
|
|
951
|
-
console.log("\n" + (0,
|
|
1186
|
+
console.log("\n" + (0, import_core14.getTerminalDivider)());
|
|
952
1187
|
console.log(import_chalk.default.bold.white(" TOP DUPLICATE PATTERNS"));
|
|
953
|
-
console.log((0,
|
|
1188
|
+
console.log((0, import_core14.getTerminalDivider)() + "\n");
|
|
954
1189
|
const topDuplicates = duplicates.sort((a, b) => {
|
|
955
|
-
const bVal = (0,
|
|
956
|
-
const aVal = (0,
|
|
1190
|
+
const bVal = (0, import_core14.getSeverityValue)(b.severity);
|
|
1191
|
+
const aVal = (0, import_core14.getSeverityValue)(a.severity);
|
|
957
1192
|
const severityDiff = bVal - aVal;
|
|
958
1193
|
if (severityDiff !== 0) return severityDiff;
|
|
959
1194
|
return b.similarity - a.similarity;
|
|
960
1195
|
}).slice(0, maxResults);
|
|
961
1196
|
topDuplicates.forEach((dup) => {
|
|
962
|
-
const severityBadge = (0,
|
|
1197
|
+
const severityBadge = (0, import_core14.getSeverityBadge)(dup.severity);
|
|
963
1198
|
const file1Name = dup.file1.split("/").pop() || dup.file1;
|
|
964
1199
|
const file2Name = dup.file2.split("/").pop() || dup.file2;
|
|
965
1200
|
console.log(
|
|
@@ -992,9 +1227,9 @@ function printRawDuplicates(duplicates, maxResults) {
|
|
|
992
1227
|
}
|
|
993
1228
|
function printCriticalIssues(issues) {
|
|
994
1229
|
if (issues.length === 0) return;
|
|
995
|
-
console.log((0,
|
|
1230
|
+
console.log((0, import_core14.getTerminalDivider)());
|
|
996
1231
|
console.log(import_chalk.default.bold.white(" CRITICAL ISSUES (>95% similar)"));
|
|
997
|
-
console.log((0,
|
|
1232
|
+
console.log((0, import_core14.getTerminalDivider)() + "\n");
|
|
998
1233
|
issues.slice(0, 5).forEach((issue) => {
|
|
999
1234
|
console.log(
|
|
1000
1235
|
import_chalk.default.red("\u25CF ") + import_chalk.default.white(`${issue.file}:${issue.location.line}`)
|
|
@@ -1018,7 +1253,7 @@ async function patternActionHandler(directory, options) {
|
|
|
1018
1253
|
} = await analyzePatterns(finalOptions);
|
|
1019
1254
|
let filteredDuplicates = rawDuplicates;
|
|
1020
1255
|
if (finalOptions.minSeverity) {
|
|
1021
|
-
filteredDuplicates = (0,
|
|
1256
|
+
filteredDuplicates = (0, import_core5.filterBySeverity)(
|
|
1022
1257
|
filteredDuplicates,
|
|
1023
1258
|
finalOptions.minSeverity
|
|
1024
1259
|
);
|
|
@@ -1066,7 +1301,7 @@ async function patternActionHandler(directory, options) {
|
|
|
1066
1301
|
);
|
|
1067
1302
|
}
|
|
1068
1303
|
function handleJsonOutput(outputFile, directory, data) {
|
|
1069
|
-
const outputPath = (0,
|
|
1304
|
+
const outputPath = (0, import_core15.resolveOutputPath)(
|
|
1070
1305
|
outputFile,
|
|
1071
1306
|
`pattern-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
|
|
1072
1307
|
directory
|
|
@@ -1079,7 +1314,7 @@ function handleJsonOutput(outputFile, directory, data) {
|
|
|
1079
1314
|
}
|
|
1080
1315
|
function handleHtmlOutput(outputFile, directory, summary, results) {
|
|
1081
1316
|
const html = generateHTMLReport(summary, results);
|
|
1082
|
-
const outputPath = (0,
|
|
1317
|
+
const outputPath = (0, import_core15.resolveOutputPath)(
|
|
1083
1318
|
outputFile,
|
|
1084
1319
|
`pattern-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.html`,
|
|
1085
1320
|
directory
|
|
@@ -1114,7 +1349,7 @@ function renderTerminalOutput(fileCount, totalIssues, summary, elapsedTime, opti
|
|
|
1114
1349
|
} else if (totalIssues < 5) {
|
|
1115
1350
|
printGuidance();
|
|
1116
1351
|
}
|
|
1117
|
-
console.log((0,
|
|
1352
|
+
console.log((0, import_core15.getTerminalDivider)());
|
|
1118
1353
|
if (totalIssues > 0) {
|
|
1119
1354
|
console.log(
|
|
1120
1355
|
import_chalk2.default.white(
|
|
@@ -1126,7 +1361,7 @@ function renderTerminalOutput(fileCount, totalIssues, summary, elapsedTime, opti
|
|
|
1126
1361
|
printFooter();
|
|
1127
1362
|
}
|
|
1128
1363
|
function resultsToCriticalIssues(summary, duplicates) {
|
|
1129
|
-
return duplicates.filter((d) => (0,
|
|
1364
|
+
return duplicates.filter((d) => (0, import_core15.getSeverityValue)(d.severity) === 4).map((d) => ({
|
|
1130
1365
|
file: d.file1,
|
|
1131
1366
|
location: { line: d.line1 },
|
|
1132
1367
|
message: `${d.patternType} pattern highly similar to ${d.file2}`,
|