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.
Files changed (57) hide show
  1. package/dist/chunks/analysis-tool-handlers-H2RXLDPX.js +817 -0
  2. package/dist/chunks/analysis-tool-handlers-RJZAR6VT.js +817 -0
  3. package/dist/chunks/analysis-tool-handlers-Z2RF24T7.js +13 -0
  4. package/dist/chunks/autodoc-tool-handlers-CV5JEQUA.js +1112 -0
  5. package/dist/chunks/autodoc-tool-handlers-EHTNCH6I.js +1112 -0
  6. package/dist/chunks/autodoc-tool-handlers-MECXQJ2K.js +138 -0
  7. package/dist/chunks/chaos-CO7TOBOJ.js +18 -0
  8. package/dist/chunks/chaos-VM2PXERO.js +1573 -0
  9. package/dist/chunks/chaos-W3XRVJ7K.js +1564 -0
  10. package/dist/chunks/chunk-6K37BWK5.js +439 -0
  11. package/dist/chunks/chunk-EALTCYHZ.js +10 -0
  12. package/dist/chunks/chunk-FTBE7VMY.js +316 -0
  13. package/dist/chunks/chunk-KBW6LRQP.js +322 -0
  14. package/dist/chunks/chunk-NKUHX4CU.js +5 -0
  15. package/dist/chunks/chunk-NZFF4DQ4.js +3179 -0
  16. package/dist/chunks/chunk-RGP5UVQ7.js +3179 -0
  17. package/dist/chunks/chunk-RMZXFGQZ.js +322 -0
  18. package/dist/chunks/chunk-UG44F23Y.js +316 -0
  19. package/dist/chunks/chunk-V2SCB5H5.js +4403 -0
  20. package/dist/chunks/chunk-V6JAQNM3.js +1 -0
  21. package/dist/chunks/chunk-XFGXM4CR.js +4403 -0
  22. package/dist/chunks/dev-agent-JVIGBMHQ.js +1 -0
  23. package/dist/chunks/dev-agent-TRVP5U6N.js +1624 -0
  24. package/dist/chunks/dev-agent-Y5G5WKQ4.js +1624 -0
  25. package/dist/chunks/graph-storage-factory-AYZ57YSL.js +13 -0
  26. package/dist/chunks/graph-storage-factory-GTAIJEI5.js +1 -0
  27. package/dist/chunks/graph-storage-factory-T2WO5QVG.js +13 -0
  28. package/dist/chunks/incremental-updater-KDIQGAUU.js +14 -0
  29. package/dist/chunks/incremental-updater-OJRSTO3Q.js +1 -0
  30. package/dist/chunks/incremental-updater-SBEBH7KF.js +14 -0
  31. package/dist/chunks/indexer-agent-H3QIEL3Z.js +21 -0
  32. package/dist/chunks/indexer-agent-KHF5JMV7.js +21 -0
  33. package/dist/chunks/indexer-agent-SHJD6Z77.js +1 -0
  34. package/dist/chunks/indexing-pipeline-J6Z4BHKF.js +1 -0
  35. package/dist/chunks/indexing-pipeline-OY3337QN.js +249 -0
  36. package/dist/chunks/indexing-pipeline-WCXIDMAP.js +249 -0
  37. package/dist/chunks/merge-agent-LSUBDJB2.js +2481 -0
  38. package/dist/chunks/merge-agent-MJEW3HWU.js +2481 -0
  39. package/dist/chunks/merge-agent-O45OXF33.js +11 -0
  40. package/dist/chunks/merge-tool-handlers-BDSVNQVZ.js +277 -0
  41. package/dist/chunks/merge-tool-handlers-HP7DRBXJ.js +1 -0
  42. package/dist/chunks/merge-tool-handlers-RUJAKE3D.js +277 -0
  43. package/dist/chunks/pattern-tool-handlers-L62W3CXR.js +1549 -0
  44. package/dist/chunks/pattern-tool-handlers-SAHX2CVW.js +13 -0
  45. package/dist/chunks/query-agent-3TWDFIMT.js +191 -0
  46. package/dist/chunks/query-agent-HXQ3BMMF.js +191 -0
  47. package/dist/chunks/query-agent-USMC2GNG.js +1 -0
  48. package/dist/chunks/semantic-agent-MQCAWIAB.js +6381 -0
  49. package/dist/chunks/semantic-agent-NDGR3NAK.js +6381 -0
  50. package/dist/chunks/semantic-agent-S4ZL6GZC.js +137 -0
  51. package/dist/index.js +17 -17
  52. package/dist/roslyn-addon/.build-hash +1 -1
  53. package/dist/roslyn-addon/ILGPU.Algorithms.dll +0 -0
  54. package/dist/roslyn-addon/ILGPU.dll +0 -0
  55. package/dist/roslyn-addon/UltraCode.CSharp.deps.json +35 -0
  56. package/dist/roslyn-addon/UltraCode.CSharp.dll +0 -0
  57. 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