ultracode 5.3.0 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/analysis-tool-handlers-H2RXLDPX.js +817 -0
- package/dist/chunks/analysis-tool-handlers-RJZAR6VT.js +817 -0
- package/dist/chunks/analysis-tool-handlers-Z2RF24T7.js +13 -0
- package/dist/chunks/autodoc-tool-handlers-CV5JEQUA.js +1112 -0
- package/dist/chunks/autodoc-tool-handlers-EHTNCH6I.js +1112 -0
- package/dist/chunks/autodoc-tool-handlers-MECXQJ2K.js +138 -0
- package/dist/chunks/chaos-CO7TOBOJ.js +18 -0
- package/dist/chunks/chaos-VM2PXERO.js +1573 -0
- package/dist/chunks/chaos-W3XRVJ7K.js +1564 -0
- package/dist/chunks/chunk-6K37BWK5.js +439 -0
- package/dist/chunks/chunk-EALTCYHZ.js +10 -0
- package/dist/chunks/chunk-FTBE7VMY.js +316 -0
- package/dist/chunks/chunk-KBW6LRQP.js +322 -0
- package/dist/chunks/chunk-NKUHX4CU.js +5 -0
- package/dist/chunks/chunk-NZFF4DQ4.js +3179 -0
- package/dist/chunks/chunk-RGP5UVQ7.js +3179 -0
- package/dist/chunks/chunk-RMZXFGQZ.js +322 -0
- package/dist/chunks/chunk-UG44F23Y.js +316 -0
- package/dist/chunks/chunk-V2SCB5H5.js +4403 -0
- package/dist/chunks/chunk-V6JAQNM3.js +1 -0
- package/dist/chunks/chunk-XFGXM4CR.js +4403 -0
- package/dist/chunks/dev-agent-JVIGBMHQ.js +1 -0
- package/dist/chunks/dev-agent-TRVP5U6N.js +1624 -0
- package/dist/chunks/dev-agent-Y5G5WKQ4.js +1624 -0
- package/dist/chunks/graph-storage-factory-AYZ57YSL.js +13 -0
- package/dist/chunks/graph-storage-factory-GTAIJEI5.js +1 -0
- package/dist/chunks/graph-storage-factory-T2WO5QVG.js +13 -0
- package/dist/chunks/incremental-updater-KDIQGAUU.js +14 -0
- package/dist/chunks/incremental-updater-OJRSTO3Q.js +1 -0
- package/dist/chunks/incremental-updater-SBEBH7KF.js +14 -0
- package/dist/chunks/indexer-agent-H3QIEL3Z.js +21 -0
- package/dist/chunks/indexer-agent-KHF5JMV7.js +21 -0
- package/dist/chunks/indexer-agent-SHJD6Z77.js +1 -0
- package/dist/chunks/indexing-pipeline-J6Z4BHKF.js +1 -0
- package/dist/chunks/indexing-pipeline-OY3337QN.js +249 -0
- package/dist/chunks/indexing-pipeline-WCXIDMAP.js +249 -0
- package/dist/chunks/merge-agent-LSUBDJB2.js +2481 -0
- package/dist/chunks/merge-agent-MJEW3HWU.js +2481 -0
- package/dist/chunks/merge-agent-O45OXF33.js +11 -0
- package/dist/chunks/merge-tool-handlers-BDSVNQVZ.js +277 -0
- package/dist/chunks/merge-tool-handlers-HP7DRBXJ.js +1 -0
- package/dist/chunks/merge-tool-handlers-RUJAKE3D.js +277 -0
- package/dist/chunks/pattern-tool-handlers-L62W3CXR.js +1549 -0
- package/dist/chunks/pattern-tool-handlers-SAHX2CVW.js +13 -0
- package/dist/chunks/query-agent-3TWDFIMT.js +191 -0
- package/dist/chunks/query-agent-HXQ3BMMF.js +191 -0
- package/dist/chunks/query-agent-USMC2GNG.js +1 -0
- package/dist/chunks/semantic-agent-MQCAWIAB.js +6381 -0
- package/dist/chunks/semantic-agent-NDGR3NAK.js +6381 -0
- package/dist/chunks/semantic-agent-S4ZL6GZC.js +137 -0
- package/dist/index.js +17 -17
- package/dist/roslyn-addon/.build-hash +1 -1
- package/dist/roslyn-addon/ILGPU.Algorithms.dll +0 -0
- package/dist/roslyn-addon/ILGPU.dll +0 -0
- package/dist/roslyn-addon/UltraCode.CSharp.deps.json +35 -0
- package/dist/roslyn-addon/UltraCode.CSharp.dll +0 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1549 @@
|
|
|
1
|
+
import { DetectPatternsSchema, CheckEntityPatternsSchema } from './chunk-NEXEPPHU.js';
|
|
2
|
+
import { BaseToolHandler } from './chunk-WLKASYMP.js';
|
|
3
|
+
import './chunk-KMXE2SJJ.js';
|
|
4
|
+
import { cosineSimilarity } from './chunk-QN237S7C.js';
|
|
5
|
+
import './chunk-ZD54CMKT.js';
|
|
6
|
+
import './chunk-HEMJHRHZ.js';
|
|
7
|
+
import { toError } from './chunk-5WKPA33T.js';
|
|
8
|
+
import { init_logging, log } from './chunk-VCCBEJQ5.js';
|
|
9
|
+
import { __export } from './chunk-NAQKA54E.js';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { createHash } from 'crypto';
|
|
12
|
+
import { readdirSync, readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
13
|
+
import YAML from 'yaml';
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
|
|
16
|
+
// src/analysis/patterns/pattern-engine.ts
|
|
17
|
+
init_logging();
|
|
18
|
+
|
|
19
|
+
// src/analysis/patterns/detectors/common.ts
|
|
20
|
+
var common_exports = {};
|
|
21
|
+
__export(common_exports, {
|
|
22
|
+
checkDeepNesting: () => checkDeepNesting,
|
|
23
|
+
checkGodFunction: () => checkGodFunction,
|
|
24
|
+
checkLargeClass: () => checkLargeClass,
|
|
25
|
+
checkNoDocumentation: () => checkNoDocumentation,
|
|
26
|
+
checkSmallFocusedFunction: () => checkSmallFocusedFunction,
|
|
27
|
+
checkTooManyParams: () => checkTooManyParams
|
|
28
|
+
});
|
|
29
|
+
function checkGodFunction(entity) {
|
|
30
|
+
const mods = entity.metadata?.modifiers ?? [];
|
|
31
|
+
if (mods.includes("partial")) return { match: false, confidence: 0 };
|
|
32
|
+
const metrics = entity.metadata?.["metrics"];
|
|
33
|
+
const cyclomatic = metrics?.cyclomaticComplexity ?? 0;
|
|
34
|
+
const loc = metrics?.linesOfCode ?? 0;
|
|
35
|
+
const matched = [];
|
|
36
|
+
if (cyclomatic > 20) matched.push(`cyclomatic=${cyclomatic}`);
|
|
37
|
+
if (loc > 200) matched.push(`LOC=${loc}`);
|
|
38
|
+
if (matched.length === 0) return { match: false, confidence: 0 };
|
|
39
|
+
const confidence = matched.length === 2 ? 0.95 : 0.8;
|
|
40
|
+
return { match: true, confidence, matchedCriteria: matched };
|
|
41
|
+
}
|
|
42
|
+
function checkDeepNesting(entity) {
|
|
43
|
+
const metrics = entity.metadata?.["metrics"];
|
|
44
|
+
const depth = metrics?.nestingDepth ?? 0;
|
|
45
|
+
if (depth <= 5) return { match: false, confidence: 0 };
|
|
46
|
+
return {
|
|
47
|
+
match: true,
|
|
48
|
+
confidence: Math.min(0.5 + (depth - 5) * 0.1, 1),
|
|
49
|
+
matchedCriteria: [`nestingDepth=${depth}`]
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function checkTooManyParams(entity) {
|
|
53
|
+
const params = entity.metadata?.parameters ?? [];
|
|
54
|
+
if (params.length <= 7) return { match: false, confidence: 0 };
|
|
55
|
+
const mods = entity.metadata?.modifiers ?? [];
|
|
56
|
+
if (mods.includes("constructor") || mods.includes("partial")) {
|
|
57
|
+
return { match: false, confidence: 0 };
|
|
58
|
+
}
|
|
59
|
+
const interfaceParams = params.filter((p) => p.type && /^I[A-Z]/.test(p.type)).length;
|
|
60
|
+
if (params.length > 0 && interfaceParams / params.length >= 0.7) {
|
|
61
|
+
return { match: false, confidence: 0 };
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
match: true,
|
|
65
|
+
confidence: Math.min(0.6 + (params.length - 7) * 0.05, 1),
|
|
66
|
+
matchedCriteria: [`paramCount=${params.length}`]
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function checkNoDocumentation(entity) {
|
|
70
|
+
const mods = entity.metadata?.modifiers ?? [];
|
|
71
|
+
const isPublic = mods.includes("public") || mods.includes("export") || mods.includes("exported");
|
|
72
|
+
if (!isPublic) return { match: false, confidence: 0 };
|
|
73
|
+
if (mods.includes("partial")) return { match: false, confidence: 0 };
|
|
74
|
+
if (mods.includes("internal")) return { match: false, confidence: 0 };
|
|
75
|
+
const fp = entity.filePath;
|
|
76
|
+
if (/[\\/](Minimal|Internal|Hosting|Kestrel|Middleware|Extensions)\b/i.test(fp)) {
|
|
77
|
+
return { match: false, confidence: 0 };
|
|
78
|
+
}
|
|
79
|
+
const signature = entity.metadata?.signature;
|
|
80
|
+
const hasJsdoc = signature?.includes("/**") ?? false;
|
|
81
|
+
const hasDocstring = entity.metadata?.["documentation"] != null || entity.metadata?.["description"] != null;
|
|
82
|
+
if (hasJsdoc || hasDocstring) return { match: false, confidence: 0 };
|
|
83
|
+
return {
|
|
84
|
+
match: true,
|
|
85
|
+
confidence: 0.7,
|
|
86
|
+
matchedCriteria: ["public-no-docs"]
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function checkSmallFocusedFunction(entity) {
|
|
90
|
+
const metrics = entity.metadata?.["metrics"];
|
|
91
|
+
const cyclomatic = metrics?.cyclomaticComplexity ?? 0;
|
|
92
|
+
const loc = metrics?.linesOfCode ?? 0;
|
|
93
|
+
if (loc < 3) return { match: false, confidence: 0 };
|
|
94
|
+
const matched = [];
|
|
95
|
+
if (loc <= 30) matched.push(`LOC=${loc}`);
|
|
96
|
+
if (cyclomatic <= 5) matched.push(`cyclomatic=${cyclomatic}`);
|
|
97
|
+
if (matched.length < 2) return { match: false, confidence: 0 };
|
|
98
|
+
return { match: true, confidence: 0.85, matchedCriteria: matched };
|
|
99
|
+
}
|
|
100
|
+
function checkLargeClass(entity) {
|
|
101
|
+
if (entity.type !== "class") return { match: false, confidence: 0 };
|
|
102
|
+
const metrics = entity.metadata?.["metrics"];
|
|
103
|
+
const loc = metrics?.linesOfCode ?? 0;
|
|
104
|
+
const methodCount = entity.metadata?.["metrics"]?.methodCount ?? 0;
|
|
105
|
+
const matched = [];
|
|
106
|
+
if (loc > 500) matched.push(`LOC=${loc}`);
|
|
107
|
+
if (methodCount > 20) matched.push(`methods=${methodCount}`);
|
|
108
|
+
if (loc <= 500) return { match: false, confidence: 0 };
|
|
109
|
+
return {
|
|
110
|
+
match: true,
|
|
111
|
+
confidence: matched.length === 2 ? 0.9 : 0.7,
|
|
112
|
+
matchedCriteria: matched
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/analysis/patterns/exemplar-store.ts
|
|
117
|
+
init_logging();
|
|
118
|
+
var ExemplarStore = class {
|
|
119
|
+
constructor(exemplarsDir, cacheDir) {
|
|
120
|
+
this.exemplarsDir = exemplarsDir;
|
|
121
|
+
this.cacheDir = cacheDir ?? join(process.cwd(), ".cache");
|
|
122
|
+
}
|
|
123
|
+
exemplars = /* @__PURE__ */ new Map();
|
|
124
|
+
exemplarsByPattern = /* @__PURE__ */ new Map();
|
|
125
|
+
embeddings = /* @__PURE__ */ new Map();
|
|
126
|
+
loaded = false;
|
|
127
|
+
embedded = false;
|
|
128
|
+
cacheDir;
|
|
129
|
+
/**
|
|
130
|
+
* Load exemplar YAML files
|
|
131
|
+
*/
|
|
132
|
+
async load(exemplarsDir) {
|
|
133
|
+
if (this.loaded) return;
|
|
134
|
+
const dir = exemplarsDir ?? this.exemplarsDir ?? join(import.meta.dirname, "exemplars");
|
|
135
|
+
let files;
|
|
136
|
+
try {
|
|
137
|
+
files = readdirSync(dir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
138
|
+
} catch {
|
|
139
|
+
log.w("EXEMPLAR_STORE", "dir_not_found", { dir });
|
|
140
|
+
this.loaded = true;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
for (const file of files) {
|
|
144
|
+
try {
|
|
145
|
+
const content = readFileSync(join(dir, file), "utf-8");
|
|
146
|
+
const raw = YAML.parse(content);
|
|
147
|
+
if (!raw?.exemplars) continue;
|
|
148
|
+
for (const ex of raw.exemplars) {
|
|
149
|
+
const exemplar = {
|
|
150
|
+
id: ex.id,
|
|
151
|
+
patternId: ex.patternId,
|
|
152
|
+
language: ex.language,
|
|
153
|
+
code: ex.code.slice(0, 400),
|
|
154
|
+
// Enforce limit
|
|
155
|
+
description: ex.description
|
|
156
|
+
};
|
|
157
|
+
this.exemplars.set(ex.id, exemplar);
|
|
158
|
+
const patternList = this.exemplarsByPattern.get(ex.patternId) ?? [];
|
|
159
|
+
patternList.push(exemplar);
|
|
160
|
+
this.exemplarsByPattern.set(ex.patternId, patternList);
|
|
161
|
+
}
|
|
162
|
+
log.i("EXEMPLAR_STORE", "loaded_file", { file, count: raw.exemplars.length });
|
|
163
|
+
} catch (err) {
|
|
164
|
+
log.e("EXEMPLAR_STORE", "load_error", { file, error: String(err) });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
this.loaded = true;
|
|
168
|
+
log.i("EXEMPLAR_STORE", "store_ready", { total: this.exemplars.size });
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Lazy-embed all exemplars (called on first detect_patterns)
|
|
172
|
+
*/
|
|
173
|
+
async ensureEmbeddings(embeddingGen) {
|
|
174
|
+
if (this.embedded || this.exemplars.size === 0) return;
|
|
175
|
+
const currentHash = this.computeHash();
|
|
176
|
+
const cacheFile = join(this.cacheDir, "pattern-exemplar-embeddings.json");
|
|
177
|
+
if (this.tryLoadCache(cacheFile, currentHash)) {
|
|
178
|
+
this.embedded = true;
|
|
179
|
+
log.i("EXEMPLAR_STORE", "cache_hit", { exemplars: this.embeddings.size });
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
log.i("EXEMPLAR_STORE", "generating_embeddings", { count: this.exemplars.size });
|
|
183
|
+
const startMs = Date.now();
|
|
184
|
+
for (const [id, exemplar] of this.exemplars) {
|
|
185
|
+
try {
|
|
186
|
+
const embedding = await embeddingGen.generateCodeEmbedding(exemplar.code, exemplar.language);
|
|
187
|
+
this.embeddings.set(id, embedding);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
log.w("EXEMPLAR_STORE", "embed_error", { id, error: String(err) });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
log.i("EXEMPLAR_STORE", "embeddings_generated", {
|
|
193
|
+
count: this.embeddings.size,
|
|
194
|
+
durationMs: Date.now() - startMs
|
|
195
|
+
});
|
|
196
|
+
this.saveCache(cacheFile, currentHash);
|
|
197
|
+
this.embedded = true;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Find similar exemplars for a given entity embedding and pattern
|
|
201
|
+
*/
|
|
202
|
+
findSimilarExemplars(entityEmbedding, patternId, limit = 1) {
|
|
203
|
+
const exemplars = this.exemplarsByPattern.get(patternId) ?? [];
|
|
204
|
+
if (exemplars.length === 0) return [];
|
|
205
|
+
const results = [];
|
|
206
|
+
for (const ex of exemplars) {
|
|
207
|
+
const exEmbedding = this.embeddings.get(ex.id);
|
|
208
|
+
if (!exEmbedding) continue;
|
|
209
|
+
const similarity = cosineSimilarity(entityEmbedding, exEmbedding);
|
|
210
|
+
results.push({ id: ex.id, similarity, description: ex.description });
|
|
211
|
+
}
|
|
212
|
+
return results.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get exemplar by ID
|
|
216
|
+
*/
|
|
217
|
+
get(id) {
|
|
218
|
+
return this.exemplars.get(id);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get exemplars for a pattern
|
|
222
|
+
*/
|
|
223
|
+
getForPattern(patternId) {
|
|
224
|
+
return this.exemplarsByPattern.get(patternId) ?? [];
|
|
225
|
+
}
|
|
226
|
+
get size() {
|
|
227
|
+
return this.exemplars.size;
|
|
228
|
+
}
|
|
229
|
+
// ─── Cache Management ────────────────────────────────────────────
|
|
230
|
+
computeHash() {
|
|
231
|
+
const codes = [...this.exemplars.values()].map((e) => e.code).sort().join("|");
|
|
232
|
+
return createHash("sha256").update(codes).digest("hex").slice(0, 16);
|
|
233
|
+
}
|
|
234
|
+
tryLoadCache(cacheFile, expectedHash) {
|
|
235
|
+
try {
|
|
236
|
+
if (!existsSync(cacheFile)) return false;
|
|
237
|
+
const data = JSON.parse(readFileSync(cacheFile, "utf-8"));
|
|
238
|
+
if (data.hash !== expectedHash) return false;
|
|
239
|
+
for (const [id, base64] of Object.entries(data.embeddings)) {
|
|
240
|
+
if (!this.exemplars.has(id)) continue;
|
|
241
|
+
const buffer = Buffer.from(base64, "base64");
|
|
242
|
+
this.embeddings.set(id, new Float32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / 4));
|
|
243
|
+
}
|
|
244
|
+
return this.embeddings.size > 0;
|
|
245
|
+
} catch {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
saveCache(cacheFile, hash) {
|
|
250
|
+
try {
|
|
251
|
+
if (!existsSync(this.cacheDir)) {
|
|
252
|
+
mkdirSync(this.cacheDir, { recursive: true });
|
|
253
|
+
}
|
|
254
|
+
const data = {
|
|
255
|
+
hash,
|
|
256
|
+
embeddings: {}
|
|
257
|
+
};
|
|
258
|
+
for (const [id, embedding] of this.embeddings) {
|
|
259
|
+
const buf = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
|
|
260
|
+
data.embeddings[id] = buf.toString("base64");
|
|
261
|
+
}
|
|
262
|
+
writeFileSync(cacheFile, JSON.stringify(data), "utf-8");
|
|
263
|
+
log.i("EXEMPLAR_STORE", "cache_saved", { file: cacheFile, count: this.embeddings.size });
|
|
264
|
+
} catch (err) {
|
|
265
|
+
log.w("EXEMPLAR_STORE", "cache_save_error", { error: String(err) });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// src/analysis/patterns/pattern-registry.ts
|
|
271
|
+
init_logging();
|
|
272
|
+
var RelationshipCriteriaSchema = z.object({
|
|
273
|
+
type: z.string(),
|
|
274
|
+
direction: z.enum(["incoming", "outgoing"]),
|
|
275
|
+
minCount: z.number().optional(),
|
|
276
|
+
maxCount: z.number().optional(),
|
|
277
|
+
crossFileRatio: z.object({
|
|
278
|
+
min: z.number().optional(),
|
|
279
|
+
max: z.number().optional()
|
|
280
|
+
}).optional()
|
|
281
|
+
});
|
|
282
|
+
var StructuralCriteriaSchema = z.object({
|
|
283
|
+
entityTypes: z.array(z.string()).optional(),
|
|
284
|
+
requiredModifiers: z.array(z.string()).optional(),
|
|
285
|
+
forbiddenModifiers: z.array(z.string()).optional(),
|
|
286
|
+
returnTypeMatch: z.string().optional(),
|
|
287
|
+
returnTypeNotMatch: z.string().optional(),
|
|
288
|
+
minParams: z.number().optional(),
|
|
289
|
+
maxParams: z.number().optional(),
|
|
290
|
+
paramTypeRequired: z.string().optional(),
|
|
291
|
+
paramTypeAbsent: z.string().optional(),
|
|
292
|
+
minCyclomatic: z.number().optional(),
|
|
293
|
+
maxCyclomatic: z.number().optional(),
|
|
294
|
+
minCognitive: z.number().optional(),
|
|
295
|
+
minNesting: z.number().optional(),
|
|
296
|
+
minLOC: z.number().optional(),
|
|
297
|
+
maxLOC: z.number().optional(),
|
|
298
|
+
hasLoops: z.boolean().optional(),
|
|
299
|
+
hasExceptions: z.boolean().optional(),
|
|
300
|
+
hasAwaits: z.boolean().optional(),
|
|
301
|
+
minBranches: z.number().optional(),
|
|
302
|
+
minCallCount: z.number().optional(),
|
|
303
|
+
callsInclude: z.array(z.string()).optional(),
|
|
304
|
+
callsExclude: z.array(z.string()).optional(),
|
|
305
|
+
decoratorMatch: z.array(z.string()).optional(),
|
|
306
|
+
hasNoInheritance: z.boolean().optional(),
|
|
307
|
+
filePathNotMatch: z.string().optional(),
|
|
308
|
+
nameMatch: z.string().optional(),
|
|
309
|
+
nameNotMatch: z.string().optional(),
|
|
310
|
+
relationships: z.array(RelationshipCriteriaSchema).optional()
|
|
311
|
+
});
|
|
312
|
+
var PatternDefinitionSchema = z.object({
|
|
313
|
+
id: z.string(),
|
|
314
|
+
language: z.string().optional(),
|
|
315
|
+
// Inferred from filename if absent
|
|
316
|
+
category: z.enum(["anti-pattern", "best-pattern", "code-smell", "optimization"]),
|
|
317
|
+
severity: z.enum(["critical", "high", "medium", "low", "info"]),
|
|
318
|
+
name: z.string(),
|
|
319
|
+
description: z.string(),
|
|
320
|
+
suggestion: z.string(),
|
|
321
|
+
bigO: z.object({
|
|
322
|
+
before: z.string(),
|
|
323
|
+
after: z.string()
|
|
324
|
+
}).optional(),
|
|
325
|
+
benchmark: z.string().optional(),
|
|
326
|
+
tags: z.array(z.string()).default([]),
|
|
327
|
+
enabled: z.boolean().default(true),
|
|
328
|
+
structural: StructuralCriteriaSchema.optional(),
|
|
329
|
+
customDetector: z.string().optional(),
|
|
330
|
+
exemplarIds: z.array(z.string()).optional(),
|
|
331
|
+
minSemanticSimilarity: z.number().default(0),
|
|
332
|
+
minStructuralConfidence: z.number().default(0.6)
|
|
333
|
+
});
|
|
334
|
+
var PatternFileSchema = z.object({
|
|
335
|
+
patterns: z.array(PatternDefinitionSchema)
|
|
336
|
+
});
|
|
337
|
+
var PatternRegistry = class {
|
|
338
|
+
constructor(rulesDir) {
|
|
339
|
+
this.rulesDir = rulesDir;
|
|
340
|
+
}
|
|
341
|
+
patterns = /* @__PURE__ */ new Map();
|
|
342
|
+
allPatterns = /* @__PURE__ */ new Map();
|
|
343
|
+
loaded = false;
|
|
344
|
+
/**
|
|
345
|
+
* Load all YAML rule files. Call once before using.
|
|
346
|
+
*/
|
|
347
|
+
async load(rulesDir) {
|
|
348
|
+
if (this.loaded) return;
|
|
349
|
+
const dir = rulesDir ?? this.rulesDir ?? join(import.meta.dirname, "rules");
|
|
350
|
+
let files;
|
|
351
|
+
try {
|
|
352
|
+
files = readdirSync(dir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
353
|
+
} catch {
|
|
354
|
+
log.w("PATTERN_REGISTRY", "rules_dir_not_found", { dir });
|
|
355
|
+
this.loaded = true;
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
for (const file of files) {
|
|
359
|
+
const language = file.replace(/\.ya?ml$/, "");
|
|
360
|
+
try {
|
|
361
|
+
const content = readFileSync(join(dir, file), "utf-8");
|
|
362
|
+
const raw = YAML.parse(content);
|
|
363
|
+
const parsed = PatternFileSchema.parse(raw);
|
|
364
|
+
const definitions = parsed.patterns.map((p) => ({
|
|
365
|
+
...p,
|
|
366
|
+
language: p.language ?? language
|
|
367
|
+
}));
|
|
368
|
+
const existing = this.patterns.get(language) ?? [];
|
|
369
|
+
this.patterns.set(language, [...existing, ...definitions]);
|
|
370
|
+
for (const def of definitions) {
|
|
371
|
+
if (this.allPatterns.has(def.id)) {
|
|
372
|
+
log.w("PATTERN_REGISTRY", "duplicate_pattern_id", { id: def.id });
|
|
373
|
+
}
|
|
374
|
+
this.allPatterns.set(def.id, def);
|
|
375
|
+
}
|
|
376
|
+
log.i("PATTERN_REGISTRY", "loaded_rules", { file, count: definitions.length });
|
|
377
|
+
} catch (err) {
|
|
378
|
+
log.e("PATTERN_REGISTRY", "load_error", { file, error: String(err) });
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
this.loaded = true;
|
|
382
|
+
log.i("PATTERN_REGISTRY", "registry_ready", {
|
|
383
|
+
languages: this.patterns.size,
|
|
384
|
+
totalPatterns: this.allPatterns.size
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Get patterns filtered by criteria. Always includes "common" patterns.
|
|
389
|
+
*/
|
|
390
|
+
getPatterns(options) {
|
|
391
|
+
const { language, category = "all", tags, enabledOnly = true } = options;
|
|
392
|
+
let result = [];
|
|
393
|
+
if (language) {
|
|
394
|
+
result = [...this.patterns.get(language) ?? [], ...this.patterns.get("common") ?? []];
|
|
395
|
+
} else {
|
|
396
|
+
result = [...this.allPatterns.values()];
|
|
397
|
+
}
|
|
398
|
+
if (enabledOnly) {
|
|
399
|
+
result = result.filter((p) => p.enabled);
|
|
400
|
+
}
|
|
401
|
+
if (category !== "all") {
|
|
402
|
+
result = result.filter((p) => p.category === category);
|
|
403
|
+
}
|
|
404
|
+
if (tags && tags.length > 0) {
|
|
405
|
+
result = result.filter((p) => tags.some((t) => p.tags.includes(t)));
|
|
406
|
+
}
|
|
407
|
+
return result;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Get a single pattern by ID
|
|
411
|
+
*/
|
|
412
|
+
getPattern(id) {
|
|
413
|
+
return this.allPatterns.get(id);
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Get all unique language keys
|
|
417
|
+
*/
|
|
418
|
+
getLanguages() {
|
|
419
|
+
return [...this.patterns.keys()];
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Total loaded pattern count
|
|
423
|
+
*/
|
|
424
|
+
get size() {
|
|
425
|
+
return this.allPatterns.size;
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
// src/analysis/patterns/semantic-validator.ts
|
|
430
|
+
init_logging();
|
|
431
|
+
var SemanticValidator = class {
|
|
432
|
+
constructor(exemplarStore, embeddingGen) {
|
|
433
|
+
this.exemplarStore = exemplarStore;
|
|
434
|
+
this.embeddingGen = embeddingGen;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Validate structural candidates with semantic similarity
|
|
438
|
+
*
|
|
439
|
+
* @returns Confirmed PatternMatch[] with combined scores
|
|
440
|
+
*/
|
|
441
|
+
async validate(candidates, _patternMap) {
|
|
442
|
+
const results = [];
|
|
443
|
+
const needsSemantic = candidates.filter((c) => c.pattern.minSemanticSimilarity > 0);
|
|
444
|
+
const structuralOnly = candidates.filter((c) => c.pattern.minSemanticSimilarity === 0);
|
|
445
|
+
for (const c of structuralOnly) {
|
|
446
|
+
results.push(this.createMatch(c, 1, void 0));
|
|
447
|
+
}
|
|
448
|
+
if (needsSemantic.length > 0) {
|
|
449
|
+
if (this.embeddingGen) {
|
|
450
|
+
await this.exemplarStore.ensureEmbeddings(this.embeddingGen);
|
|
451
|
+
}
|
|
452
|
+
for (const c of needsSemantic) {
|
|
453
|
+
const entityEmbedding = await this.getEntityEmbedding(c);
|
|
454
|
+
if (!entityEmbedding) {
|
|
455
|
+
if (c.confidence >= c.pattern.minStructuralConfidence) {
|
|
456
|
+
results.push(this.createMatch(c, -1, void 0));
|
|
457
|
+
}
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
const exemplarResults = this.exemplarStore.findSimilarExemplars(entityEmbedding, c.pattern.id, 1);
|
|
461
|
+
const topExemplar = exemplarResults[0];
|
|
462
|
+
const similarity = topExemplar?.similarity ?? 0;
|
|
463
|
+
if (similarity >= c.pattern.minSemanticSimilarity) {
|
|
464
|
+
results.push(this.createMatch(c, similarity, topExemplar));
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
log.i("SEMANTIC_VALIDATOR", "validation_complete", {
|
|
469
|
+
candidates: candidates.length,
|
|
470
|
+
confirmed: results.length,
|
|
471
|
+
structuralOnly: structuralOnly.length,
|
|
472
|
+
semanticValidated: needsSemantic.length
|
|
473
|
+
});
|
|
474
|
+
return results;
|
|
475
|
+
}
|
|
476
|
+
// ─── Helpers ──────────────────────────────────────────────────────
|
|
477
|
+
createMatch(candidate, semanticSimilarity, closestExemplar) {
|
|
478
|
+
const isStructuralOnly = candidate.pattern.minSemanticSimilarity === 0;
|
|
479
|
+
const noSemanticData = semanticSimilarity < 0;
|
|
480
|
+
const combinedScore = isStructuralOnly || noSemanticData ? candidate.confidence : candidate.confidence * 0.4 + semanticSimilarity * 0.6;
|
|
481
|
+
return {
|
|
482
|
+
patternId: candidate.pattern.id,
|
|
483
|
+
pattern: candidate.pattern,
|
|
484
|
+
entityId: candidate.entity.id,
|
|
485
|
+
entityName: candidate.entity.name,
|
|
486
|
+
entityType: candidate.entity.type,
|
|
487
|
+
filePath: candidate.entity.filePath,
|
|
488
|
+
line: candidate.entity.location?.start?.line ?? 0,
|
|
489
|
+
structuralConfidence: candidate.confidence,
|
|
490
|
+
semanticSimilarity: isStructuralOnly ? 1 : noSemanticData ? 0 : semanticSimilarity,
|
|
491
|
+
combinedScore,
|
|
492
|
+
matchedCriteria: candidate.matchedCriteria,
|
|
493
|
+
closestExemplar: closestExemplar ? { id: closestExemplar.id, similarity: closestExemplar.similarity, description: closestExemplar.description } : void 0
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
async getEntityEmbedding(candidate) {
|
|
497
|
+
const base64 = candidate.entity.embeddingBase64;
|
|
498
|
+
if (base64) {
|
|
499
|
+
try {
|
|
500
|
+
const buffer = Buffer.from(base64, "base64");
|
|
501
|
+
return new Float32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / 4);
|
|
502
|
+
} catch {
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (this.embeddingGen) {
|
|
506
|
+
try {
|
|
507
|
+
const text = candidate.entity.embeddingText ?? candidate.entity.name;
|
|
508
|
+
const language = candidate.entity.language ?? candidate.entity.metadata?.language;
|
|
509
|
+
return await this.embeddingGen.generateCodeEmbedding(text, language);
|
|
510
|
+
} catch (err) {
|
|
511
|
+
log.w("SEMANTIC_VALIDATOR", "embed_error", {
|
|
512
|
+
entity: candidate.entity.id,
|
|
513
|
+
error: String(err)
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
// src/analysis/patterns/structural-detector.ts
|
|
522
|
+
init_logging();
|
|
523
|
+
var detectorRegistry = /* @__PURE__ */ new Map();
|
|
524
|
+
function registerDetectors(detectors) {
|
|
525
|
+
for (const [name, fn] of Object.entries(detectors)) {
|
|
526
|
+
detectorRegistry.set(name, fn);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
var compiledCache = /* @__PURE__ */ new WeakMap();
|
|
530
|
+
function getCompiled(pattern) {
|
|
531
|
+
let compiled = compiledCache.get(pattern);
|
|
532
|
+
if (compiled) return compiled;
|
|
533
|
+
const c = pattern.structural;
|
|
534
|
+
compiled = {
|
|
535
|
+
entityTypeSet: c?.entityTypes ? new Set(c.entityTypes) : null,
|
|
536
|
+
returnTypeMatchRe: c?.returnTypeMatch ? new RegExp(c.returnTypeMatch, "i") : null,
|
|
537
|
+
returnTypeNotMatchRe: c?.returnTypeNotMatch ? new RegExp(c.returnTypeNotMatch, "i") : null,
|
|
538
|
+
paramTypeRequiredRe: c?.paramTypeRequired ? new RegExp(c.paramTypeRequired, "i") : null,
|
|
539
|
+
paramTypeAbsentRe: c?.paramTypeAbsent ? new RegExp(c.paramTypeAbsent, "i") : null,
|
|
540
|
+
callsIncludeRe: c?.callsInclude ? c.callsInclude.map((p) => new RegExp(p, "i")) : null,
|
|
541
|
+
callsExcludeRe: c?.callsExclude ? c.callsExclude.map((p) => new RegExp(p, "i")) : null,
|
|
542
|
+
decoratorMatchRe: c?.decoratorMatch ? c.decoratorMatch.map((p) => new RegExp(p, "i")) : null,
|
|
543
|
+
nameMatchRe: c?.nameMatch ? new RegExp(c.nameMatch, "i") : null,
|
|
544
|
+
nameNotMatchRe: c?.nameNotMatch ? new RegExp(c.nameNotMatch, "i") : null,
|
|
545
|
+
filePathNotMatchRe: c?.filePathNotMatch ? new RegExp(c.filePathNotMatch, "i") : null,
|
|
546
|
+
totalCriteriaCount: countTotalCriteria(c ?? {})
|
|
547
|
+
};
|
|
548
|
+
compiledCache.set(pattern, compiled);
|
|
549
|
+
return compiled;
|
|
550
|
+
}
|
|
551
|
+
var metaCache = /* @__PURE__ */ new WeakMap();
|
|
552
|
+
function getMeta(entity) {
|
|
553
|
+
let meta = metaCache.get(entity);
|
|
554
|
+
if (meta) return meta;
|
|
555
|
+
const md = entity.metadata;
|
|
556
|
+
const metricsRaw = md?.["metrics"];
|
|
557
|
+
const cfRaw = md?.["controlFlow"];
|
|
558
|
+
const callsRaw = md?.["calls"] ?? [];
|
|
559
|
+
const decsRaw = md?.decorators ?? [];
|
|
560
|
+
const inheritanceRaw = md?.["inheritance"];
|
|
561
|
+
meta = {
|
|
562
|
+
modifiers: md?.modifiers ?? [],
|
|
563
|
+
returnType: md?.returnType,
|
|
564
|
+
params: md?.parameters ?? [],
|
|
565
|
+
metrics: {
|
|
566
|
+
cyclomaticComplexity: metricsRaw?.["cyclomaticComplexity"] ?? 0,
|
|
567
|
+
cognitiveComplexity: metricsRaw?.["cognitiveComplexity"] ?? 0,
|
|
568
|
+
linesOfCode: metricsRaw?.["linesOfCode"] ?? 0,
|
|
569
|
+
nestingDepth: metricsRaw?.["nestingDepth"] ?? 0
|
|
570
|
+
},
|
|
571
|
+
cf: {
|
|
572
|
+
branches: cfRaw?.["branches"]?.length ?? 0,
|
|
573
|
+
loops: cfRaw?.["loops"]?.length ?? 0,
|
|
574
|
+
exceptions: cfRaw?.["exceptions"]?.length ?? 0,
|
|
575
|
+
awaits: cfRaw?.["awaits"]?.length ?? 0
|
|
576
|
+
},
|
|
577
|
+
callNames: callsRaw.map((c) => `${c.target ?? ""}.${c.name ?? ""}`),
|
|
578
|
+
decoratorNames: decsRaw.map((d) => d.name),
|
|
579
|
+
hasInheritance: (inheritanceRaw?.baseClasses?.length ?? 0) > 0 || (inheritanceRaw?.interfaces?.length ?? 0) > 0
|
|
580
|
+
};
|
|
581
|
+
metaCache.set(entity, meta);
|
|
582
|
+
return meta;
|
|
583
|
+
}
|
|
584
|
+
var EVAL_ZERO = { confidence: 0, matchedCriteria: [] };
|
|
585
|
+
var StructuralDetector = class {
|
|
586
|
+
// Cache: entityId:patternId -> evaluation result (3B)
|
|
587
|
+
evalCache = /* @__PURE__ */ new Map();
|
|
588
|
+
/** Clear evaluation cache (call when entities or patterns change) */
|
|
589
|
+
clearEvalCache() {
|
|
590
|
+
this.evalCache.clear();
|
|
591
|
+
}
|
|
592
|
+
async detect(entities, patterns, storage) {
|
|
593
|
+
const candidates = [];
|
|
594
|
+
const entitiesByType = /* @__PURE__ */ new Map();
|
|
595
|
+
for (const entity of entities) {
|
|
596
|
+
const arr = entitiesByType.get(entity.type);
|
|
597
|
+
if (arr) arr.push(entity);
|
|
598
|
+
else entitiesByType.set(entity.type, [entity]);
|
|
599
|
+
}
|
|
600
|
+
const metadataCandidates = [];
|
|
601
|
+
for (const pattern of patterns) {
|
|
602
|
+
const compiled = getCompiled(pattern);
|
|
603
|
+
let entitiesToCheck;
|
|
604
|
+
if (compiled.entityTypeSet) {
|
|
605
|
+
entitiesToCheck = [];
|
|
606
|
+
for (const type of compiled.entityTypeSet) {
|
|
607
|
+
const arr = entitiesByType.get(type);
|
|
608
|
+
if (arr) entitiesToCheck.push(...arr);
|
|
609
|
+
}
|
|
610
|
+
} else {
|
|
611
|
+
entitiesToCheck = entities;
|
|
612
|
+
}
|
|
613
|
+
for (const entity of entitiesToCheck) {
|
|
614
|
+
const result = this.evaluateMetadataCriteria(entity, pattern, compiled);
|
|
615
|
+
if (result.confidence > 0) {
|
|
616
|
+
metadataCandidates.push({
|
|
617
|
+
entity,
|
|
618
|
+
pattern,
|
|
619
|
+
confidence: result.confidence,
|
|
620
|
+
matched: result.matchedCriteria
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const needsGraph = metadataCandidates.filter(
|
|
626
|
+
(c) => c.pattern.structural?.relationships && c.pattern.structural.relationships.length > 0
|
|
627
|
+
);
|
|
628
|
+
const noGraph = metadataCandidates.filter(
|
|
629
|
+
(c) => !c.pattern.structural?.relationships || c.pattern.structural.relationships.length === 0
|
|
630
|
+
);
|
|
631
|
+
for (const c of noGraph) {
|
|
632
|
+
if (c.confidence >= c.pattern.minStructuralConfidence) {
|
|
633
|
+
candidates.push({
|
|
634
|
+
entity: c.entity,
|
|
635
|
+
pattern: c.pattern,
|
|
636
|
+
confidence: c.confidence,
|
|
637
|
+
matchedCriteria: c.matched
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (needsGraph.length > 0 && storage) {
|
|
642
|
+
const entityIds = [...new Set(needsGraph.map((c) => c.entity.id))];
|
|
643
|
+
let allRels = [];
|
|
644
|
+
try {
|
|
645
|
+
const [outgoing, incoming] = await Promise.all([
|
|
646
|
+
storage.findRelationships({ filters: { fromId: entityIds }, limit: 1e4 }),
|
|
647
|
+
storage.findRelationships({ filters: { toId: entityIds }, limit: 1e4 })
|
|
648
|
+
]);
|
|
649
|
+
allRels = [...outgoing, ...incoming];
|
|
650
|
+
} catch {
|
|
651
|
+
}
|
|
652
|
+
const relsByEntity = /* @__PURE__ */ new Map();
|
|
653
|
+
for (const rel of allRels) {
|
|
654
|
+
const fromArr = relsByEntity.get(rel.fromId);
|
|
655
|
+
if (fromArr) fromArr.push(rel);
|
|
656
|
+
else relsByEntity.set(rel.fromId, [rel]);
|
|
657
|
+
if (rel.fromId !== rel.toId) {
|
|
658
|
+
const toArr = relsByEntity.get(rel.toId);
|
|
659
|
+
if (toArr) toArr.push(rel);
|
|
660
|
+
else relsByEntity.set(rel.toId, [rel]);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
for (const c of needsGraph) {
|
|
664
|
+
const rels = relsByEntity.get(c.entity.id) ?? [];
|
|
665
|
+
const graphResult = this.evaluateRelationshipCriteria(c.entity, rels, c.pattern.structural.relationships);
|
|
666
|
+
if (graphResult.passed) {
|
|
667
|
+
const totalMatched = c.matched.length + graphResult.matchedCriteria.length;
|
|
668
|
+
const compiled = getCompiled(c.pattern);
|
|
669
|
+
const adjustedConfidence = totalMatched / Math.max(compiled.totalCriteriaCount, 1);
|
|
670
|
+
if (adjustedConfidence >= c.pattern.minStructuralConfidence) {
|
|
671
|
+
candidates.push({
|
|
672
|
+
entity: c.entity,
|
|
673
|
+
pattern: c.pattern,
|
|
674
|
+
confidence: Math.min(adjustedConfidence, 1),
|
|
675
|
+
matchedCriteria: [...c.matched, ...graphResult.matchedCriteria]
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
} else if (needsGraph.length > 0) {
|
|
681
|
+
for (const c of needsGraph) {
|
|
682
|
+
if (c.confidence >= c.pattern.minStructuralConfidence) {
|
|
683
|
+
candidates.push({
|
|
684
|
+
entity: c.entity,
|
|
685
|
+
pattern: c.pattern,
|
|
686
|
+
confidence: c.confidence,
|
|
687
|
+
matchedCriteria: c.matched
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
log.i("STRUCTURAL_DETECTOR", "detection_complete", {
|
|
693
|
+
entities: entities.length,
|
|
694
|
+
patterns: patterns.length,
|
|
695
|
+
candidates: candidates.length
|
|
696
|
+
});
|
|
697
|
+
return candidates;
|
|
698
|
+
}
|
|
699
|
+
// ─── Metadata Evaluation (hot path — optimized) ─────────────────
|
|
700
|
+
evaluateMetadataCriteria(entity, pattern, compiled) {
|
|
701
|
+
const cacheKey = `${entity.id}::${pattern.id}`;
|
|
702
|
+
const cached = this.evalCache.get(cacheKey);
|
|
703
|
+
if (cached) return cached;
|
|
704
|
+
const result = this.evaluateMetadataUncached(entity, pattern, compiled);
|
|
705
|
+
this.evalCache.set(cacheKey, result);
|
|
706
|
+
return result;
|
|
707
|
+
}
|
|
708
|
+
// 3A: Mandatory checks separated for fast bail-out
|
|
709
|
+
evaluateRequired(entity, compiled, criteria, em) {
|
|
710
|
+
const matched = [];
|
|
711
|
+
if (compiled.entityTypeSet) {
|
|
712
|
+
matched.push(`entityType:${entity.type}`);
|
|
713
|
+
}
|
|
714
|
+
if (criteria.requiredModifiers) {
|
|
715
|
+
for (const req of criteria.requiredModifiers) {
|
|
716
|
+
if (!em.modifiers.includes(req)) return null;
|
|
717
|
+
}
|
|
718
|
+
matched.push(`modifiers:${criteria.requiredModifiers.join(",")}`);
|
|
719
|
+
}
|
|
720
|
+
if (criteria.forbiddenModifiers) {
|
|
721
|
+
for (const f of criteria.forbiddenModifiers) {
|
|
722
|
+
if (em.modifiers.includes(f)) return null;
|
|
723
|
+
}
|
|
724
|
+
matched.push("no-forbidden-modifiers");
|
|
725
|
+
}
|
|
726
|
+
if (criteria.hasNoInheritance) {
|
|
727
|
+
if (em.hasInheritance) return null;
|
|
728
|
+
matched.push("no-inheritance");
|
|
729
|
+
}
|
|
730
|
+
if (compiled.filePathNotMatchRe) {
|
|
731
|
+
if (entity.filePath && compiled.filePathNotMatchRe.test(entity.filePath)) return null;
|
|
732
|
+
}
|
|
733
|
+
if (compiled.nameNotMatchRe) {
|
|
734
|
+
if (compiled.nameNotMatchRe.test(entity.name)) return null;
|
|
735
|
+
}
|
|
736
|
+
return matched;
|
|
737
|
+
}
|
|
738
|
+
// 3A: Optional checks separated from mandatory
|
|
739
|
+
evaluateOptional(entity, compiled, criteria, em, matched) {
|
|
740
|
+
let optionalTotal = 0;
|
|
741
|
+
let optionalPassed = 0;
|
|
742
|
+
if (compiled.returnTypeMatchRe) {
|
|
743
|
+
optionalTotal++;
|
|
744
|
+
if (typeof em.returnType === "string" && compiled.returnTypeMatchRe.test(em.returnType)) {
|
|
745
|
+
optionalPassed++;
|
|
746
|
+
matched.push(`returnType:~/${criteria.returnTypeMatch}/`);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
if (compiled.returnTypeNotMatchRe) {
|
|
750
|
+
optionalTotal++;
|
|
751
|
+
if (typeof em.returnType === "string" && !compiled.returnTypeNotMatchRe.test(em.returnType)) {
|
|
752
|
+
optionalPassed++;
|
|
753
|
+
matched.push(`returnType:!~/${criteria.returnTypeNotMatch}/`);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
if (criteria.minParams != null) {
|
|
757
|
+
optionalTotal++;
|
|
758
|
+
if (em.params.length >= criteria.minParams) {
|
|
759
|
+
optionalPassed++;
|
|
760
|
+
matched.push(`params>=${criteria.minParams}`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
if (criteria.maxParams != null) {
|
|
764
|
+
optionalTotal++;
|
|
765
|
+
if (em.params.length <= criteria.maxParams) {
|
|
766
|
+
optionalPassed++;
|
|
767
|
+
matched.push(`params<=${criteria.maxParams}`);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
if (compiled.paramTypeRequiredRe) {
|
|
771
|
+
optionalTotal++;
|
|
772
|
+
if (em.params.some((p) => p.type && compiled.paramTypeRequiredRe.test(p.type))) {
|
|
773
|
+
optionalPassed++;
|
|
774
|
+
matched.push(`paramType:${criteria.paramTypeRequired}`);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
if (compiled.paramTypeAbsentRe) {
|
|
778
|
+
optionalTotal++;
|
|
779
|
+
if (!em.params.some((p) => p.type && compiled.paramTypeAbsentRe.test(p.type))) {
|
|
780
|
+
optionalPassed++;
|
|
781
|
+
matched.push(`paramType:!${criteria.paramTypeAbsent}`);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
if (criteria.minCyclomatic != null) {
|
|
785
|
+
optionalTotal++;
|
|
786
|
+
if (em.metrics.cyclomaticComplexity >= criteria.minCyclomatic) {
|
|
787
|
+
optionalPassed++;
|
|
788
|
+
matched.push(`cyclomatic>=${criteria.minCyclomatic}`);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
if (criteria.maxCyclomatic != null) {
|
|
792
|
+
optionalTotal++;
|
|
793
|
+
if (em.metrics.cyclomaticComplexity <= criteria.maxCyclomatic) {
|
|
794
|
+
optionalPassed++;
|
|
795
|
+
matched.push(`cyclomatic<=${criteria.maxCyclomatic}`);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (criteria.minCognitive != null) {
|
|
799
|
+
optionalTotal++;
|
|
800
|
+
if (em.metrics.cognitiveComplexity >= criteria.minCognitive) {
|
|
801
|
+
optionalPassed++;
|
|
802
|
+
matched.push(`cognitive>=${criteria.minCognitive}`);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
if (criteria.minNesting != null) {
|
|
806
|
+
optionalTotal++;
|
|
807
|
+
if (em.metrics.nestingDepth >= criteria.minNesting) {
|
|
808
|
+
optionalPassed++;
|
|
809
|
+
matched.push(`nesting>=${criteria.minNesting}`);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (criteria.minLOC != null) {
|
|
813
|
+
optionalTotal++;
|
|
814
|
+
if (em.metrics.linesOfCode >= criteria.minLOC) {
|
|
815
|
+
optionalPassed++;
|
|
816
|
+
matched.push(`LOC>=${criteria.minLOC}`);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
if (criteria.maxLOC != null) {
|
|
820
|
+
optionalTotal++;
|
|
821
|
+
if (em.metrics.linesOfCode <= criteria.maxLOC) {
|
|
822
|
+
optionalPassed++;
|
|
823
|
+
matched.push(`LOC<=${criteria.maxLOC}`);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
if (criteria.hasLoops != null) {
|
|
827
|
+
optionalTotal++;
|
|
828
|
+
if (em.cf.loops > 0 === criteria.hasLoops) {
|
|
829
|
+
optionalPassed++;
|
|
830
|
+
matched.push(criteria.hasLoops ? "hasLoops" : "noLoops");
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (criteria.hasExceptions != null) {
|
|
834
|
+
optionalTotal++;
|
|
835
|
+
if (em.cf.exceptions > 0 === criteria.hasExceptions) {
|
|
836
|
+
optionalPassed++;
|
|
837
|
+
matched.push(criteria.hasExceptions ? "hasExceptions" : "noExceptions");
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (criteria.hasAwaits != null) {
|
|
841
|
+
optionalTotal++;
|
|
842
|
+
if (em.cf.awaits > 0 === criteria.hasAwaits) {
|
|
843
|
+
optionalPassed++;
|
|
844
|
+
matched.push(criteria.hasAwaits ? "hasAwaits" : "noAwaits");
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
if (criteria.minBranches != null) {
|
|
848
|
+
optionalTotal++;
|
|
849
|
+
if (em.cf.branches >= criteria.minBranches) {
|
|
850
|
+
optionalPassed++;
|
|
851
|
+
matched.push(`branches>=${criteria.minBranches}`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
if (criteria.minCallCount != null) {
|
|
855
|
+
optionalTotal++;
|
|
856
|
+
if (em.callNames.length >= criteria.minCallCount) {
|
|
857
|
+
optionalPassed++;
|
|
858
|
+
matched.push(`calls>=${criteria.minCallCount}`);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
if (compiled.callsIncludeRe) {
|
|
862
|
+
for (let i = 0; i < compiled.callsIncludeRe.length; i++) {
|
|
863
|
+
optionalTotal++;
|
|
864
|
+
if (em.callNames.some((c) => compiled.callsIncludeRe[i].test(c))) {
|
|
865
|
+
optionalPassed++;
|
|
866
|
+
matched.push(`calls:~/${criteria.callsInclude[i]}/`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
if (compiled.callsExcludeRe) {
|
|
871
|
+
for (let i = 0; i < compiled.callsExcludeRe.length; i++) {
|
|
872
|
+
optionalTotal++;
|
|
873
|
+
if (!em.callNames.some((c) => compiled.callsExcludeRe[i].test(c))) {
|
|
874
|
+
optionalPassed++;
|
|
875
|
+
matched.push(`calls:!~/${criteria.callsExclude[i]}/`);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
if (compiled.decoratorMatchRe) {
|
|
880
|
+
for (let i = 0; i < compiled.decoratorMatchRe.length; i++) {
|
|
881
|
+
optionalTotal++;
|
|
882
|
+
if (em.decoratorNames.some((d) => compiled.decoratorMatchRe[i].test(d))) {
|
|
883
|
+
optionalPassed++;
|
|
884
|
+
matched.push(`decorator:~/${criteria.decoratorMatch[i]}/`);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
if (compiled.nameMatchRe) {
|
|
889
|
+
optionalTotal++;
|
|
890
|
+
if (compiled.nameMatchRe.test(entity.name)) {
|
|
891
|
+
optionalPassed++;
|
|
892
|
+
matched.push(`name:~/${criteria.nameMatch}/`);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
if (compiled.nameNotMatchRe) {
|
|
896
|
+
matched.push(`name:!~/${criteria.nameNotMatch}/`);
|
|
897
|
+
}
|
|
898
|
+
return { optionalTotal, optionalPassed };
|
|
899
|
+
}
|
|
900
|
+
evaluateMetadataUncached(entity, pattern, compiled) {
|
|
901
|
+
const criteria = pattern.structural;
|
|
902
|
+
if (!criteria && !pattern.customDetector) {
|
|
903
|
+
return EVAL_ZERO;
|
|
904
|
+
}
|
|
905
|
+
const matched = [];
|
|
906
|
+
const em = getMeta(entity);
|
|
907
|
+
if (criteria) {
|
|
908
|
+
const requiredMatched = this.evaluateRequired(entity, compiled, criteria, em);
|
|
909
|
+
if (requiredMatched === null) return EVAL_ZERO;
|
|
910
|
+
matched.push(...requiredMatched);
|
|
911
|
+
const { optionalTotal, optionalPassed } = this.evaluateOptional(entity, compiled, criteria, em, matched);
|
|
912
|
+
const mandatoryCount = requiredMatched.length;
|
|
913
|
+
if (optionalTotal === 0) {
|
|
914
|
+
if (matched.length > 0 && !pattern.customDetector) {
|
|
915
|
+
return { confidence: 1, matchedCriteria: matched };
|
|
916
|
+
}
|
|
917
|
+
} else {
|
|
918
|
+
const conf = optionalPassed / optionalTotal;
|
|
919
|
+
if ((conf > 0 || matched.length > mandatoryCount) && !pattern.customDetector) {
|
|
920
|
+
return { confidence: conf, matchedCriteria: matched };
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
if (pattern.customDetector) {
|
|
925
|
+
const detector = detectorRegistry.get(pattern.customDetector);
|
|
926
|
+
if (detector) {
|
|
927
|
+
try {
|
|
928
|
+
const result = detector(entity);
|
|
929
|
+
if (result.match) {
|
|
930
|
+
const customMatched = result.matchedCriteria ?? [`custom:${pattern.customDetector}`];
|
|
931
|
+
const allMatched = [...matched, ...customMatched];
|
|
932
|
+
const mandatoryCount = matched.filter(
|
|
933
|
+
(m) => m.startsWith("entityType:") || m.startsWith("modifiers:") || m === "no-forbidden-modifiers"
|
|
934
|
+
).length;
|
|
935
|
+
const hasOptionalStructural = matched.length > mandatoryCount;
|
|
936
|
+
const blended = hasOptionalStructural ? (result.confidence + matched.length / Math.max(compiled.totalCriteriaCount, 1)) / 2 : result.confidence;
|
|
937
|
+
return { confidence: blended, matchedCriteria: allMatched };
|
|
938
|
+
}
|
|
939
|
+
return EVAL_ZERO;
|
|
940
|
+
} catch (err) {
|
|
941
|
+
log.w("STRUCTURAL_DETECTOR", "custom_detector_error", {
|
|
942
|
+
detector: pattern.customDetector,
|
|
943
|
+
error: String(err)
|
|
944
|
+
});
|
|
945
|
+
return EVAL_ZERO;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
return EVAL_ZERO;
|
|
949
|
+
}
|
|
950
|
+
if (matched.length > 0 && !criteria) {
|
|
951
|
+
return { confidence: 0.5, matchedCriteria: matched };
|
|
952
|
+
}
|
|
953
|
+
return {
|
|
954
|
+
confidence: matched.length > 0 ? matched.length / Math.max(compiled.totalCriteriaCount, 1) : 0,
|
|
955
|
+
matchedCriteria: matched
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
// ─── Relationship Evaluation ─────────────────────────────────────
|
|
959
|
+
evaluateRelationshipCriteria(entity, relationships, criteria) {
|
|
960
|
+
const matched = [];
|
|
961
|
+
for (const crit of criteria) {
|
|
962
|
+
const filtered = relationships.filter((r) => {
|
|
963
|
+
const matchesType = r.type === crit.type;
|
|
964
|
+
const matchesDirection = crit.direction === "outgoing" ? r.fromId === entity.id : r.toId === entity.id;
|
|
965
|
+
return matchesType && matchesDirection;
|
|
966
|
+
});
|
|
967
|
+
const count = filtered.length;
|
|
968
|
+
if (crit.minCount != null && count < crit.minCount) {
|
|
969
|
+
return { passed: false, matchedCriteria: matched };
|
|
970
|
+
}
|
|
971
|
+
if (crit.maxCount != null && count > crit.maxCount) {
|
|
972
|
+
return { passed: false, matchedCriteria: matched };
|
|
973
|
+
}
|
|
974
|
+
if (crit.crossFileRatio) {
|
|
975
|
+
const total = relationships.filter(
|
|
976
|
+
(r) => crit.direction === "outgoing" ? r.fromId === entity.id : r.toId === entity.id
|
|
977
|
+
).length;
|
|
978
|
+
if (total > 0) {
|
|
979
|
+
const ratio = filtered.length / total;
|
|
980
|
+
if (crit.crossFileRatio.min != null && ratio < crit.crossFileRatio.min) {
|
|
981
|
+
return { passed: false, matchedCriteria: matched };
|
|
982
|
+
}
|
|
983
|
+
if (crit.crossFileRatio.max != null && ratio > crit.crossFileRatio.max) {
|
|
984
|
+
return { passed: false, matchedCriteria: matched };
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
matched.push(`rel:${crit.direction}:${crit.type}=${count}`);
|
|
989
|
+
}
|
|
990
|
+
return { passed: true, matchedCriteria: matched };
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
function countTotalCriteria(criteria) {
|
|
994
|
+
let count = 0;
|
|
995
|
+
if (criteria.entityTypes) count++;
|
|
996
|
+
if (criteria.requiredModifiers) count++;
|
|
997
|
+
if (criteria.forbiddenModifiers) count++;
|
|
998
|
+
if (criteria.returnTypeMatch) count++;
|
|
999
|
+
if (criteria.returnTypeNotMatch) count++;
|
|
1000
|
+
if (criteria.minParams != null) count++;
|
|
1001
|
+
if (criteria.maxParams != null) count++;
|
|
1002
|
+
if (criteria.paramTypeRequired) count++;
|
|
1003
|
+
if (criteria.paramTypeAbsent) count++;
|
|
1004
|
+
if (criteria.minCyclomatic != null) count++;
|
|
1005
|
+
if (criteria.maxCyclomatic != null) count++;
|
|
1006
|
+
if (criteria.minCognitive != null) count++;
|
|
1007
|
+
if (criteria.minNesting != null) count++;
|
|
1008
|
+
if (criteria.minLOC != null) count++;
|
|
1009
|
+
if (criteria.maxLOC != null) count++;
|
|
1010
|
+
if (criteria.hasLoops != null) count++;
|
|
1011
|
+
if (criteria.hasExceptions != null) count++;
|
|
1012
|
+
if (criteria.hasAwaits != null) count++;
|
|
1013
|
+
if (criteria.minBranches != null) count++;
|
|
1014
|
+
if (criteria.minCallCount != null) count++;
|
|
1015
|
+
if (criteria.callsInclude) count += criteria.callsInclude.length;
|
|
1016
|
+
if (criteria.callsExclude) count += criteria.callsExclude.length;
|
|
1017
|
+
if (criteria.decoratorMatch) count += criteria.decoratorMatch.length;
|
|
1018
|
+
if (criteria.hasNoInheritance) count++;
|
|
1019
|
+
if (criteria.filePathNotMatch) count++;
|
|
1020
|
+
if (criteria.nameMatch) count++;
|
|
1021
|
+
if (criteria.nameNotMatch) count++;
|
|
1022
|
+
if (criteria.relationships) count += criteria.relationships.length;
|
|
1023
|
+
return count;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// src/analysis/patterns/pattern-engine.ts
|
|
1027
|
+
var SEVERITY_WEIGHTS = {
|
|
1028
|
+
critical: 4,
|
|
1029
|
+
high: 3,
|
|
1030
|
+
medium: 2,
|
|
1031
|
+
low: 1,
|
|
1032
|
+
info: 0
|
|
1033
|
+
};
|
|
1034
|
+
var LANGUAGE_DETECTOR_MAP = {
|
|
1035
|
+
typescript: "./detectors/typescript.js",
|
|
1036
|
+
javascript: "./detectors/typescript.js",
|
|
1037
|
+
// JS uses same detectors as TS
|
|
1038
|
+
python: "./detectors/python.js",
|
|
1039
|
+
csharp: "./detectors/csharp.js",
|
|
1040
|
+
java: "./detectors/java.js",
|
|
1041
|
+
kotlin: "./detectors/java.js",
|
|
1042
|
+
// Kotlin uses same detectors as Java
|
|
1043
|
+
go: "./detectors/go.js"
|
|
1044
|
+
};
|
|
1045
|
+
var PatternEngine = class {
|
|
1046
|
+
constructor(embeddingGen) {
|
|
1047
|
+
this.embeddingGen = embeddingGen;
|
|
1048
|
+
}
|
|
1049
|
+
registry = new PatternRegistry();
|
|
1050
|
+
exemplarStore = new ExemplarStore();
|
|
1051
|
+
structuralDetector = new StructuralDetector();
|
|
1052
|
+
semanticValidator = null;
|
|
1053
|
+
initialized = false;
|
|
1054
|
+
loadedDetectorModules = /* @__PURE__ */ new Set();
|
|
1055
|
+
/**
|
|
1056
|
+
* Lazy initialization — loads YAML rules, exemplars, and common detectors.
|
|
1057
|
+
* Language-specific detectors are loaded on-demand per scan.
|
|
1058
|
+
*/
|
|
1059
|
+
async initialize() {
|
|
1060
|
+
if (this.initialized) return;
|
|
1061
|
+
const baseDir = import.meta.dirname;
|
|
1062
|
+
await this.registry.load(join(baseDir, "rules"));
|
|
1063
|
+
await this.exemplarStore.load(join(baseDir, "exemplars"));
|
|
1064
|
+
registerDetectors(common_exports);
|
|
1065
|
+
this.semanticValidator = new SemanticValidator(this.exemplarStore, this.embeddingGen);
|
|
1066
|
+
this.initialized = true;
|
|
1067
|
+
log.i("PATTERN_ENGINE", "initialized", {
|
|
1068
|
+
patterns: this.registry.size,
|
|
1069
|
+
exemplars: this.exemplarStore.size
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Load language-specific detectors on demand (cached — each module loaded only once)
|
|
1074
|
+
*/
|
|
1075
|
+
async ensureDetectorsForLanguage(language) {
|
|
1076
|
+
if (!language) return;
|
|
1077
|
+
const modulePath = LANGUAGE_DETECTOR_MAP[language.toLowerCase()];
|
|
1078
|
+
if (!modulePath || this.loadedDetectorModules.has(modulePath)) return;
|
|
1079
|
+
try {
|
|
1080
|
+
const detectors = await import(modulePath);
|
|
1081
|
+
registerDetectors(detectors);
|
|
1082
|
+
this.loadedDetectorModules.add(modulePath);
|
|
1083
|
+
log.i("PATTERN_ENGINE", "loaded_detectors", { language, module: modulePath });
|
|
1084
|
+
} catch {
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Full scan — the main entry point
|
|
1089
|
+
*/
|
|
1090
|
+
async scan(options, storage) {
|
|
1091
|
+
await this.initialize();
|
|
1092
|
+
const startMs = Date.now();
|
|
1093
|
+
const {
|
|
1094
|
+
filePath,
|
|
1095
|
+
language,
|
|
1096
|
+
category = "all",
|
|
1097
|
+
tags,
|
|
1098
|
+
minConfidence = 0.5,
|
|
1099
|
+
severity = "all",
|
|
1100
|
+
offset = 0,
|
|
1101
|
+
limit = 50,
|
|
1102
|
+
entityLimit = 1e4,
|
|
1103
|
+
suppressPatterns
|
|
1104
|
+
} = options;
|
|
1105
|
+
const dbLimit = entityLimit;
|
|
1106
|
+
let entities;
|
|
1107
|
+
if (filePath) {
|
|
1108
|
+
const isDirectory = /[\\/]$/.test(filePath) || !/\.\w+$/.test(filePath.split(/[\\/]/).pop() ?? "");
|
|
1109
|
+
if (isDirectory && typeof storage.searchEntitiesInDirectory === "function") {
|
|
1110
|
+
entities = await storage.searchEntitiesInDirectory(filePath);
|
|
1111
|
+
} else {
|
|
1112
|
+
entities = await storage.findEntities({ filters: { filePath }, limit: dbLimit });
|
|
1113
|
+
}
|
|
1114
|
+
} else {
|
|
1115
|
+
entities = await storage.findEntities({ limit: dbLimit });
|
|
1116
|
+
}
|
|
1117
|
+
if (entities.length === 0) {
|
|
1118
|
+
return this.emptyResult();
|
|
1119
|
+
}
|
|
1120
|
+
const detectedLanguage = language ?? this.detectLanguage(entities);
|
|
1121
|
+
await this.ensureDetectorsForLanguage(detectedLanguage);
|
|
1122
|
+
let patterns = this.registry.getPatterns({
|
|
1123
|
+
language: detectedLanguage,
|
|
1124
|
+
category,
|
|
1125
|
+
tags,
|
|
1126
|
+
enabledOnly: true
|
|
1127
|
+
});
|
|
1128
|
+
if (suppressPatterns?.length) {
|
|
1129
|
+
const suppressSet = new Set(suppressPatterns);
|
|
1130
|
+
patterns = patterns.filter((p) => !suppressSet.has(p.id));
|
|
1131
|
+
}
|
|
1132
|
+
if (patterns.length === 0) {
|
|
1133
|
+
return this.emptyResult(entities.length);
|
|
1134
|
+
}
|
|
1135
|
+
const candidates = await this.structuralDetector.detect(entities, patterns, storage);
|
|
1136
|
+
const patternMap = new Map(patterns.map((p) => [p.id, p]));
|
|
1137
|
+
const confirmed = this.semanticValidator ? await this.semanticValidator.validate(candidates, patternMap) : candidates.map(
|
|
1138
|
+
(c) => ({
|
|
1139
|
+
patternId: c.pattern.id,
|
|
1140
|
+
pattern: c.pattern,
|
|
1141
|
+
entityId: c.entity.id,
|
|
1142
|
+
entityName: c.entity.name,
|
|
1143
|
+
entityType: c.entity.type,
|
|
1144
|
+
filePath: c.entity.filePath,
|
|
1145
|
+
line: c.entity.location?.start?.line ?? 0,
|
|
1146
|
+
structuralConfidence: c.confidence,
|
|
1147
|
+
semanticSimilarity: 1,
|
|
1148
|
+
combinedScore: c.confidence,
|
|
1149
|
+
matchedCriteria: c.matchedCriteria
|
|
1150
|
+
})
|
|
1151
|
+
);
|
|
1152
|
+
let filtered = confirmed.filter((m) => m.combinedScore >= minConfidence);
|
|
1153
|
+
if (severity !== "all") {
|
|
1154
|
+
filtered = filtered.filter((m) => m.pattern.severity === severity);
|
|
1155
|
+
}
|
|
1156
|
+
filtered.sort((a, b) => b.combinedScore - a.combinedScore);
|
|
1157
|
+
const antiPatterns = filtered.filter((m) => m.pattern.category === "anti-pattern");
|
|
1158
|
+
const bestPatterns = filtered.filter((m) => m.pattern.category === "best-pattern");
|
|
1159
|
+
const codeSmells = filtered.filter((m) => m.pattern.category === "code-smell");
|
|
1160
|
+
const optimizations = filtered.filter((m) => m.pattern.category === "optimization");
|
|
1161
|
+
const healthScore = this.computeHealthScore(antiPatterns, bestPatterns, codeSmells);
|
|
1162
|
+
const topIssues = this.computeTopIssues([...antiPatterns, ...codeSmells, ...optimizations]);
|
|
1163
|
+
const applyPagination = (arr) => arr.slice(offset, offset + limit);
|
|
1164
|
+
const result = {
|
|
1165
|
+
antiPatterns: applyPagination(antiPatterns),
|
|
1166
|
+
bestPatterns: applyPagination(bestPatterns),
|
|
1167
|
+
codeSmells: applyPagination(codeSmells),
|
|
1168
|
+
optimizations: applyPagination(optimizations),
|
|
1169
|
+
summary: {
|
|
1170
|
+
totalEntitiesScanned: entities.length,
|
|
1171
|
+
antiPatternCount: antiPatterns.length,
|
|
1172
|
+
bestPatternCount: bestPatterns.length,
|
|
1173
|
+
codeSmellCount: codeSmells.length,
|
|
1174
|
+
optimizationCount: optimizations.length,
|
|
1175
|
+
topIssues,
|
|
1176
|
+
healthScore
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
log.i("PATTERN_ENGINE", "scan_complete", {
|
|
1180
|
+
entities: entities.length,
|
|
1181
|
+
patterns: patterns.length,
|
|
1182
|
+
candidates: candidates.length,
|
|
1183
|
+
confirmed: confirmed.length,
|
|
1184
|
+
filtered: filtered.length,
|
|
1185
|
+
durationMs: Date.now() - startMs
|
|
1186
|
+
});
|
|
1187
|
+
return result;
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Check patterns for a specific entity
|
|
1191
|
+
*/
|
|
1192
|
+
async checkEntity(entityId, storage, category = "all") {
|
|
1193
|
+
await this.initialize();
|
|
1194
|
+
const entity = await storage.getEntity(entityId);
|
|
1195
|
+
if (!entity) return [];
|
|
1196
|
+
const language = entity.language ?? entity.metadata?.language;
|
|
1197
|
+
await this.ensureDetectorsForLanguage(language);
|
|
1198
|
+
const patterns = this.registry.getPatterns({ language, category, enabledOnly: true });
|
|
1199
|
+
const candidates = await this.structuralDetector.detect([entity], patterns, storage);
|
|
1200
|
+
const patternMap = new Map(patterns.map((p) => [p.id, p]));
|
|
1201
|
+
return this.semanticValidator ? await this.semanticValidator.validate(candidates, patternMap) : candidates.map(
|
|
1202
|
+
(c) => ({
|
|
1203
|
+
patternId: c.pattern.id,
|
|
1204
|
+
pattern: c.pattern,
|
|
1205
|
+
entityId: c.entity.id,
|
|
1206
|
+
entityName: c.entity.name,
|
|
1207
|
+
entityType: c.entity.type,
|
|
1208
|
+
filePath: c.entity.filePath,
|
|
1209
|
+
line: c.entity.location?.start?.line ?? 0,
|
|
1210
|
+
structuralConfidence: c.confidence,
|
|
1211
|
+
semanticSimilarity: 1,
|
|
1212
|
+
combinedScore: c.confidence,
|
|
1213
|
+
matchedCriteria: c.matchedCriteria
|
|
1214
|
+
})
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
// ─── Helpers ──────────────────────────────────────────────────────
|
|
1218
|
+
detectLanguage(entities) {
|
|
1219
|
+
const langCounts = /* @__PURE__ */ new Map();
|
|
1220
|
+
for (const e of entities) {
|
|
1221
|
+
const lang = e.language ?? e.metadata?.language;
|
|
1222
|
+
if (lang) {
|
|
1223
|
+
langCounts.set(lang, (langCounts.get(lang) ?? 0) + 1);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
if (langCounts.size === 0) return void 0;
|
|
1227
|
+
return [...langCounts.entries()].sort((a, b) => b[1] - a[1])[0][0];
|
|
1228
|
+
}
|
|
1229
|
+
computeHealthScore(antiPatterns, bestPatterns, codeSmells) {
|
|
1230
|
+
let antiWeight = 0;
|
|
1231
|
+
for (const m of antiPatterns) {
|
|
1232
|
+
antiWeight += SEVERITY_WEIGHTS[m.pattern.severity] ?? 1;
|
|
1233
|
+
}
|
|
1234
|
+
for (const m of codeSmells) {
|
|
1235
|
+
antiWeight += (SEVERITY_WEIGHTS[m.pattern.severity] ?? 1) * 0.5;
|
|
1236
|
+
}
|
|
1237
|
+
const bestWeight = bestPatterns.length;
|
|
1238
|
+
const score = Math.round(100 * (bestWeight + 1) / (bestWeight + antiWeight + 1));
|
|
1239
|
+
return Math.max(0, Math.min(100, score));
|
|
1240
|
+
}
|
|
1241
|
+
computeTopIssues(matches) {
|
|
1242
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1243
|
+
for (const m of matches) {
|
|
1244
|
+
const existing = counts.get(m.patternId);
|
|
1245
|
+
if (existing) {
|
|
1246
|
+
existing.count++;
|
|
1247
|
+
} else {
|
|
1248
|
+
counts.set(m.patternId, { count: 1, severity: m.pattern.severity });
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
return [...counts.entries()].map(([patternId, data]) => ({ patternId, ...data })).sort((a, b) => {
|
|
1252
|
+
const severityDiff = (SEVERITY_WEIGHTS[b.severity] ?? 0) - (SEVERITY_WEIGHTS[a.severity] ?? 0);
|
|
1253
|
+
return severityDiff !== 0 ? severityDiff : b.count - a.count;
|
|
1254
|
+
}).slice(0, 10);
|
|
1255
|
+
}
|
|
1256
|
+
emptyResult(scanned = 0) {
|
|
1257
|
+
return {
|
|
1258
|
+
antiPatterns: [],
|
|
1259
|
+
bestPatterns: [],
|
|
1260
|
+
codeSmells: [],
|
|
1261
|
+
optimizations: [],
|
|
1262
|
+
summary: {
|
|
1263
|
+
totalEntitiesScanned: scanned,
|
|
1264
|
+
antiPatternCount: 0,
|
|
1265
|
+
bestPatternCount: 0,
|
|
1266
|
+
codeSmellCount: 0,
|
|
1267
|
+
optimizationCount: 0,
|
|
1268
|
+
topIssues: [],
|
|
1269
|
+
healthScore: 100
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
|
|
1275
|
+
// src/analysis/patterns/pattern-formatter.ts
|
|
1276
|
+
var PatternFormatter = class _PatternFormatter {
|
|
1277
|
+
/**
|
|
1278
|
+
* Format scan result based on output format
|
|
1279
|
+
*/
|
|
1280
|
+
static format(result, format = "summary") {
|
|
1281
|
+
if (format === "json") {
|
|
1282
|
+
return JSON.stringify(result, null, 2);
|
|
1283
|
+
}
|
|
1284
|
+
if (format === "detailed") {
|
|
1285
|
+
return _PatternFormatter.formatDetailed(result);
|
|
1286
|
+
}
|
|
1287
|
+
return _PatternFormatter.formatSummary(result);
|
|
1288
|
+
}
|
|
1289
|
+
static formatSummary(result) {
|
|
1290
|
+
const { summary } = result;
|
|
1291
|
+
const lines = [];
|
|
1292
|
+
lines.push(`## Pattern Scan Summary`);
|
|
1293
|
+
lines.push(`- Entities scanned: ${summary.totalEntitiesScanned}`);
|
|
1294
|
+
lines.push(`- Health score: ${summary.healthScore}/100`);
|
|
1295
|
+
lines.push(``);
|
|
1296
|
+
if (summary.antiPatternCount > 0) {
|
|
1297
|
+
lines.push(`### Anti-patterns: ${summary.antiPatternCount}`);
|
|
1298
|
+
for (const m of result.antiPatterns.slice(0, 10)) {
|
|
1299
|
+
lines.push(
|
|
1300
|
+
` - **${m.pattern.name}** in \`${m.entityName}\` (${m.filePath}:${m.line}) \u2014 score: ${m.combinedScore.toFixed(2)}`
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
lines.push(``);
|
|
1304
|
+
}
|
|
1305
|
+
if (summary.codeSmellCount > 0) {
|
|
1306
|
+
lines.push(`### Code smells: ${summary.codeSmellCount}`);
|
|
1307
|
+
for (const m of result.codeSmells.slice(0, 10)) {
|
|
1308
|
+
lines.push(
|
|
1309
|
+
` - **${m.pattern.name}** in \`${m.entityName}\` (${m.filePath}:${m.line}) \u2014 score: ${m.combinedScore.toFixed(2)}`
|
|
1310
|
+
);
|
|
1311
|
+
}
|
|
1312
|
+
lines.push(``);
|
|
1313
|
+
}
|
|
1314
|
+
if (summary.optimizationCount > 0) {
|
|
1315
|
+
lines.push(`### Optimizations: ${summary.optimizationCount}`);
|
|
1316
|
+
for (const m of result.optimizations.slice(0, 10)) {
|
|
1317
|
+
const bigO = m.pattern.bigO ? ` [${m.pattern.bigO.before} \u2192 ${m.pattern.bigO.after}]` : "";
|
|
1318
|
+
const bench = m.pattern.benchmark ? ` (${m.pattern.benchmark})` : "";
|
|
1319
|
+
lines.push(` - **${m.pattern.name}**${bigO}${bench} in \`${m.entityName}\` (${m.filePath}:${m.line})`);
|
|
1320
|
+
}
|
|
1321
|
+
lines.push(``);
|
|
1322
|
+
}
|
|
1323
|
+
if (summary.bestPatternCount > 0) {
|
|
1324
|
+
lines.push(`### Best patterns: ${summary.bestPatternCount}`);
|
|
1325
|
+
for (const m of result.bestPatterns.slice(0, 5)) {
|
|
1326
|
+
lines.push(` - **${m.pattern.name}** in \`${m.entityName}\` (${m.filePath}:${m.line})`);
|
|
1327
|
+
}
|
|
1328
|
+
lines.push(``);
|
|
1329
|
+
}
|
|
1330
|
+
if (summary.topIssues.length > 0) {
|
|
1331
|
+
lines.push(`### Top Issues`);
|
|
1332
|
+
for (const issue of summary.topIssues.slice(0, 5)) {
|
|
1333
|
+
lines.push(` - \`${issue.patternId}\` \u2014 ${issue.count} occurrence(s), severity: ${issue.severity}`);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
return lines.join("\n");
|
|
1337
|
+
}
|
|
1338
|
+
static formatDetailed(result) {
|
|
1339
|
+
const all = [
|
|
1340
|
+
...result.antiPatterns,
|
|
1341
|
+
...result.codeSmells,
|
|
1342
|
+
...result.optimizations,
|
|
1343
|
+
...result.bestPatterns
|
|
1344
|
+
];
|
|
1345
|
+
const lines = [];
|
|
1346
|
+
lines.push(`## Pattern Scan \u2014 Detailed (${all.length} matches, health: ${result.summary.healthScore}/100)`);
|
|
1347
|
+
lines.push(``);
|
|
1348
|
+
for (const m of all) {
|
|
1349
|
+
const icon = m.pattern.category === "anti-pattern" ? "\u{1F534}" : m.pattern.category === "code-smell" ? "\u{1F7E1}" : m.pattern.category === "optimization" ? "\u26A1" : "\u{1F7E2}";
|
|
1350
|
+
lines.push(`### ${icon} ${m.pattern.name} [\`${m.patternId}\`]`);
|
|
1351
|
+
lines.push(`- **Category**: ${m.pattern.category} | **Severity**: ${m.pattern.severity}`);
|
|
1352
|
+
lines.push(`- **Entity**: \`${m.entityName}\` (${m.entityType}) at ${m.filePath}:${m.line}`);
|
|
1353
|
+
lines.push(
|
|
1354
|
+
`- **Score**: combined=${m.combinedScore.toFixed(2)}, structural=${m.structuralConfidence.toFixed(2)}, semantic=${m.semanticSimilarity.toFixed(2)}`
|
|
1355
|
+
);
|
|
1356
|
+
lines.push(`- **Description**: ${m.pattern.description}`);
|
|
1357
|
+
lines.push(`- **Suggestion**: ${m.pattern.suggestion}`);
|
|
1358
|
+
if (m.pattern.bigO) {
|
|
1359
|
+
lines.push(`- **Complexity**: ${m.pattern.bigO.before} \u2192 ${m.pattern.bigO.after}`);
|
|
1360
|
+
}
|
|
1361
|
+
if (m.pattern.benchmark) {
|
|
1362
|
+
lines.push(`- **Benchmark**: ${m.pattern.benchmark}`);
|
|
1363
|
+
}
|
|
1364
|
+
if (m.closestExemplar) {
|
|
1365
|
+
lines.push(
|
|
1366
|
+
`- **Closest exemplar**: ${m.closestExemplar.id} (similarity: ${m.closestExemplar.similarity.toFixed(2)}) \u2014 ${m.closestExemplar.description}`
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
if (m.matchedCriteria.length > 0) {
|
|
1370
|
+
lines.push(`- **Matched criteria**: ${m.matchedCriteria.join(", ")}`);
|
|
1371
|
+
}
|
|
1372
|
+
lines.push(``);
|
|
1373
|
+
}
|
|
1374
|
+
return lines.join("\n");
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Format for JSON API response (used by MCP handlers)
|
|
1378
|
+
*/
|
|
1379
|
+
static toJSON(result) {
|
|
1380
|
+
return {
|
|
1381
|
+
summary: result.summary,
|
|
1382
|
+
antiPatterns: result.antiPatterns.map(_PatternFormatter.matchToJSON),
|
|
1383
|
+
bestPatterns: result.bestPatterns.map(_PatternFormatter.matchToJSON),
|
|
1384
|
+
codeSmells: result.codeSmells.map(_PatternFormatter.matchToJSON),
|
|
1385
|
+
optimizations: result.optimizations.map(_PatternFormatter.matchToJSON)
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
static matchToJSON(m) {
|
|
1389
|
+
return {
|
|
1390
|
+
patternId: m.patternId,
|
|
1391
|
+
category: m.pattern.category,
|
|
1392
|
+
severity: m.pattern.severity,
|
|
1393
|
+
name: m.pattern.name,
|
|
1394
|
+
description: m.pattern.description,
|
|
1395
|
+
suggestion: m.pattern.suggestion,
|
|
1396
|
+
bigO: m.pattern.bigO,
|
|
1397
|
+
benchmark: m.pattern.benchmark,
|
|
1398
|
+
entityId: m.entityId,
|
|
1399
|
+
entityName: m.entityName,
|
|
1400
|
+
entityType: m.entityType,
|
|
1401
|
+
filePath: m.filePath,
|
|
1402
|
+
line: m.line,
|
|
1403
|
+
combinedScore: Number(m.combinedScore.toFixed(3)),
|
|
1404
|
+
structuralConfidence: Number(m.structuralConfidence.toFixed(3)),
|
|
1405
|
+
semanticSimilarity: Number(m.semanticSimilarity.toFixed(3)),
|
|
1406
|
+
matchedCriteria: m.matchedCriteria,
|
|
1407
|
+
closestExemplar: m.closestExemplar
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
};
|
|
1411
|
+
|
|
1412
|
+
// src/tools/handlers/pattern-tool-handlers.ts
|
|
1413
|
+
init_logging();
|
|
1414
|
+
var sharedEngine = null;
|
|
1415
|
+
function getEngine(embeddingGen) {
|
|
1416
|
+
if (!sharedEngine) {
|
|
1417
|
+
sharedEngine = new PatternEngine(embeddingGen);
|
|
1418
|
+
}
|
|
1419
|
+
return sharedEngine;
|
|
1420
|
+
}
|
|
1421
|
+
var DetectPatternsToolHandler = class extends BaseToolHandler {
|
|
1422
|
+
parseArgs(args) {
|
|
1423
|
+
return DetectPatternsSchema.parse(args);
|
|
1424
|
+
}
|
|
1425
|
+
async execute(args) {
|
|
1426
|
+
try {
|
|
1427
|
+
const storage = await this.ensureGraphStorageForProject(args.projectPath);
|
|
1428
|
+
let embeddingGen;
|
|
1429
|
+
try {
|
|
1430
|
+
const semanticAgent = await this.context.getSemanticAgent();
|
|
1431
|
+
embeddingGen = semanticAgent.embeddingGenerator ?? semanticAgent.getEmbeddingGenerator?.();
|
|
1432
|
+
} catch {
|
|
1433
|
+
}
|
|
1434
|
+
const engine = getEngine(embeddingGen);
|
|
1435
|
+
const options = {
|
|
1436
|
+
projectPath: this.resolveProjectPath(args),
|
|
1437
|
+
filePath: args.filePath,
|
|
1438
|
+
language: args.language,
|
|
1439
|
+
category: args.category,
|
|
1440
|
+
tags: args.tags,
|
|
1441
|
+
minConfidence: args.minConfidence,
|
|
1442
|
+
severity: args.severity === "all" ? "all" : args.severity,
|
|
1443
|
+
offset: args.offset,
|
|
1444
|
+
limit: args.limit,
|
|
1445
|
+
entityLimit: args.entityLimit,
|
|
1446
|
+
suppressPatterns: args.suppressPatterns
|
|
1447
|
+
};
|
|
1448
|
+
const result = await engine.scan(options, storage);
|
|
1449
|
+
const nextSteps = [];
|
|
1450
|
+
const allMatches = [
|
|
1451
|
+
...result.antiPatterns,
|
|
1452
|
+
...result.bestPatterns,
|
|
1453
|
+
...result.codeSmells,
|
|
1454
|
+
...result.optimizations
|
|
1455
|
+
];
|
|
1456
|
+
const hasSecurityPatterns = allMatches.some(
|
|
1457
|
+
(m) => m.pattern.tags.some((t) => /security|injection|xss|auth/i.test(t))
|
|
1458
|
+
);
|
|
1459
|
+
if (hasSecurityPatterns) {
|
|
1460
|
+
nextSteps.push("taint_analysis() \u2014 deep security analysis of detected vulnerable patterns");
|
|
1461
|
+
}
|
|
1462
|
+
nextSteps.push("graph_metrics({metric:'pagerank'}) \u2014 rank affected entities by importance");
|
|
1463
|
+
let output;
|
|
1464
|
+
if (args.format === "json") {
|
|
1465
|
+
const json = PatternFormatter.toJSON(result);
|
|
1466
|
+
json["nextSteps"] = nextSteps;
|
|
1467
|
+
output = JSON.stringify(json, null, 2);
|
|
1468
|
+
} else if (args.format === "detailed") {
|
|
1469
|
+
output = PatternFormatter.format(result, "detailed");
|
|
1470
|
+
output += `
|
|
1471
|
+
|
|
1472
|
+
---
|
|
1473
|
+
Next steps:
|
|
1474
|
+
${nextSteps.map((s) => `- ${s}`).join("\n")}`;
|
|
1475
|
+
} else {
|
|
1476
|
+
output = PatternFormatter.format(result, "summary");
|
|
1477
|
+
output += `
|
|
1478
|
+
|
|
1479
|
+
---
|
|
1480
|
+
Next steps:
|
|
1481
|
+
${nextSteps.map((s) => `- ${s}`).join("\n")}`;
|
|
1482
|
+
}
|
|
1483
|
+
return {
|
|
1484
|
+
content: [{ type: "text", text: output }]
|
|
1485
|
+
};
|
|
1486
|
+
} catch (err) {
|
|
1487
|
+
const error = toError(err);
|
|
1488
|
+
log.e("DETECT_PATTERNS", "scan_error", { error: error.message });
|
|
1489
|
+
return {
|
|
1490
|
+
content: [{ type: "text", text: `Error: ${error.message}` }]
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
var CheckEntityPatternsToolHandler = class extends BaseToolHandler {
|
|
1496
|
+
parseArgs(args) {
|
|
1497
|
+
return CheckEntityPatternsSchema.parse(args);
|
|
1498
|
+
}
|
|
1499
|
+
async execute(args) {
|
|
1500
|
+
try {
|
|
1501
|
+
const storage = await this.ensureGraphStorageForProject(args.projectPath);
|
|
1502
|
+
let embeddingGen;
|
|
1503
|
+
try {
|
|
1504
|
+
const semanticAgent = await this.context.getSemanticAgent();
|
|
1505
|
+
embeddingGen = semanticAgent.embeddingGenerator ?? semanticAgent.getEmbeddingGenerator?.();
|
|
1506
|
+
} catch {
|
|
1507
|
+
}
|
|
1508
|
+
const engine = getEngine(embeddingGen);
|
|
1509
|
+
const matches = await engine.checkEntity(args.entityId, storage, args.category);
|
|
1510
|
+
if (matches.length === 0) {
|
|
1511
|
+
return {
|
|
1512
|
+
content: [{ type: "text", text: `No pattern matches found for entity ${args.entityId}` }]
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
const output = {
|
|
1516
|
+
entityId: args.entityId,
|
|
1517
|
+
matchCount: matches.length,
|
|
1518
|
+
matches: matches.map((m) => ({
|
|
1519
|
+
patternId: m.patternId,
|
|
1520
|
+
category: m.pattern.category,
|
|
1521
|
+
severity: m.pattern.severity,
|
|
1522
|
+
name: m.pattern.name,
|
|
1523
|
+
description: m.pattern.description,
|
|
1524
|
+
suggestion: m.pattern.suggestion,
|
|
1525
|
+
bigO: m.pattern.bigO,
|
|
1526
|
+
benchmark: m.pattern.benchmark,
|
|
1527
|
+
combinedScore: Number(m.combinedScore.toFixed(3)),
|
|
1528
|
+
structuralConfidence: Number(m.structuralConfidence.toFixed(3)),
|
|
1529
|
+
semanticSimilarity: Number(m.semanticSimilarity.toFixed(3)),
|
|
1530
|
+
matchedCriteria: m.matchedCriteria,
|
|
1531
|
+
closestExemplar: m.closestExemplar
|
|
1532
|
+
}))
|
|
1533
|
+
};
|
|
1534
|
+
return {
|
|
1535
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }]
|
|
1536
|
+
};
|
|
1537
|
+
} catch (err) {
|
|
1538
|
+
const error = toError(err);
|
|
1539
|
+
log.e("CHECK_ENTITY_PATTERNS", "check_error", { error: error.message });
|
|
1540
|
+
return {
|
|
1541
|
+
content: [{ type: "text", text: `Error: ${error.message}` }]
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
};
|
|
1546
|
+
|
|
1547
|
+
export { CheckEntityPatternsToolHandler, DetectPatternsToolHandler };
|
|
1548
|
+
//# sourceMappingURL=pattern-tool-handlers-L62W3CXR.js.map
|
|
1549
|
+
//# sourceMappingURL=pattern-tool-handlers-L62W3CXR.js.map
|