ruvector 0.2.28 → 0.2.30

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 (41) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +2270 -2270
  3. package/bin/cli.js +9598 -9479
  4. package/bin/mcp-server.js +1 -1
  5. package/dist/core/intelligence-engine.d.ts +13 -0
  6. package/dist/core/intelligence-engine.d.ts.map +1 -1
  7. package/dist/core/intelligence-engine.js +38 -0
  8. package/dist/core/onnx/bundled-parallel.mjs +164 -164
  9. package/dist/core/onnx/embed-worker.mjs +67 -67
  10. package/dist/core/onnx/loader.js +434 -434
  11. package/dist/core/onnx/package.json +3 -3
  12. package/dist/core/onnx/pkg/LICENSE +21 -21
  13. package/dist/core/onnx/pkg/loader.js +348 -348
  14. package/dist/core/onnx/pkg/package.json +3 -3
  15. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm.d.ts +112 -112
  16. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm.js +5 -5
  17. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm_bg.js +638 -638
  18. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm_bg.wasm.d.ts +29 -29
  19. package/dist/core/parallel-workers.js +439 -439
  20. package/dist/workers/benchmark.js +15 -15
  21. package/package.json +122 -122
  22. package/src/decompiler/api-prober.js +302 -302
  23. package/src/decompiler/index.js +463 -463
  24. package/src/decompiler/metrics.js +86 -86
  25. package/src/decompiler/model-decompiler.js +423 -423
  26. package/src/decompiler/module-splitter.js +498 -498
  27. package/src/decompiler/module-tree.js +142 -142
  28. package/src/decompiler/name-predictor.js +400 -400
  29. package/src/decompiler/npm-fetch.js +176 -176
  30. package/src/decompiler/reconstructor.js +499 -499
  31. package/src/decompiler/reference-tracker.js +285 -285
  32. package/src/decompiler/statement-parser.js +285 -285
  33. package/src/decompiler/style-improver.js +438 -438
  34. package/src/decompiler/subcategories.js +339 -339
  35. package/src/decompiler/validator.js +379 -379
  36. package/src/decompiler/witness.js +140 -140
  37. package/wasm/package.json +26 -26
  38. package/wasm/ruvector_decompiler_wasm.d.ts +27 -27
  39. package/wasm/ruvector_decompiler_wasm.js +220 -220
  40. package/wasm/ruvector_decompiler_wasm_bg.wasm.d.ts +16 -16
  41. package/dist/core/onnx/pkg/ruvector.db +0 -0
@@ -1,400 +1,400 @@
1
- /**
2
- * name-predictor.js - Pattern-based name prediction for minified identifiers.
3
- *
4
- * Uses the 210+ training patterns from claude-code-patterns.json to infer
5
- * meaningful names based on context strings, property accesses, and
6
- * structural patterns found near each identifier.
7
- *
8
- * Falls back to structural heuristics when no pattern matches.
9
- */
10
-
11
- 'use strict';
12
-
13
- const fs = require('fs');
14
- const path = require('path');
15
-
16
- /** Cached patterns array — loaded once. */
17
- let _cachedPatterns = null;
18
-
19
- /**
20
- * Load patterns from the JSON training data.
21
- * @param {string} [patternPath] - override path to patterns JSON
22
- * @returns {Array<{context_strings: string[], property_names: string[], inferred_name: string, module_hint: string, confidence: number}>}
23
- */
24
- function loadPatterns(patternPath) {
25
- if (_cachedPatterns && !patternPath) return _cachedPatterns;
26
-
27
- const defaultPath = path.resolve(
28
- __dirname,
29
- '../../../../../crates/ruvector-decompiler/data/claude-code-patterns.json',
30
- );
31
- const resolved = patternPath || defaultPath;
32
-
33
- try {
34
- const raw = fs.readFileSync(resolved, 'utf-8');
35
- _cachedPatterns = JSON.parse(raw);
36
- return _cachedPatterns;
37
- } catch {
38
- // Pattern file not found — return empty, rely on structural rules
39
- _cachedPatterns = [];
40
- return _cachedPatterns;
41
- }
42
- }
43
-
44
- /**
45
- * Structural rename rules — deterministic, no model needed.
46
- * Ordered by specificity (most specific first).
47
- */
48
- const STRUCTURAL_RULES = [
49
- // Async generators
50
- {
51
- test: (decl) => /async\s+function\s*\*/.test(decl),
52
- nameFrom: (decl, ctx) => {
53
- if (ctx.some((s) => s.includes('agent')) && ctx.some((s) => s.includes('loop'))) return 'agentLoop';
54
- if (ctx.some((s) => s.includes('stream'))) return 'streamGenerator';
55
- return 'asyncGenerator';
56
- },
57
- confidence: 0.7,
58
- },
59
- // Error subclasses
60
- {
61
- test: (decl) => /class\s+\w+\s+extends\s+Error/.test(decl),
62
- nameFrom: (decl) => {
63
- const match = decl.match(/class\s+(\w+)\s+extends\s+(\w*Error)/);
64
- if (match && !isMinifiedLike(match[1])) return match[1];
65
- return 'CustomError';
66
- },
67
- confidence: 0.85,
68
- },
69
- // Class extending another class
70
- {
71
- test: (decl) => /class\s+\w+\s+extends\s+\w+/.test(decl),
72
- nameFrom: (decl) => {
73
- const match = decl.match(/class\s+(\w+)\s+extends\s+(\w+)/);
74
- if (match && !isMinifiedLike(match[1])) return match[1];
75
- if (match) return `${match[2]}Subclass`;
76
- return null;
77
- },
78
- confidence: 0.75,
79
- },
80
- // Regular class
81
- {
82
- test: (decl) => /class\s+\w+/.test(decl),
83
- nameFrom: (decl) => {
84
- const match = decl.match(/class\s+(\w+)/);
85
- if (match && !isMinifiedLike(match[1])) return match[1];
86
- return null;
87
- },
88
- confidence: 0.7,
89
- },
90
- // Export default function
91
- {
92
- test: (decl) => /export\s+default\s+function/.test(decl),
93
- nameFrom: () => 'defaultExport',
94
- confidence: 0.5,
95
- },
96
- // Named function
97
- {
98
- test: (decl) => /function\s+([a-zA-Z_$]\w+)/.test(decl),
99
- nameFrom: (decl) => {
100
- const match = decl.match(/function\s+([a-zA-Z_$]\w+)/);
101
- if (match && !isMinifiedLike(match[1])) return match[1];
102
- return null;
103
- },
104
- confidence: 0.8,
105
- },
106
- ];
107
-
108
- /**
109
- * Parameter naming rules based on usage context.
110
- */
111
- const PARAM_RULES = [
112
- { context: ['.messages', 'systemPrompt', 'canUseTool'], name: 'params', type: 'AgentLoopParams' },
113
- { context: ['.method', '.url', '.headers'], name: 'request', type: 'Request' },
114
- { context: ['.status', '.json', '.send'], name: 'response', type: 'Response' },
115
- { context: ['.next', '.done', '.value'], name: 'iterator', type: 'Iterator' },
116
- { context: ['.emit', '.on', '.removeListener'], name: 'emitter', type: 'EventEmitter' },
117
- { context: ['.pipe', '.write', '.end'], name: 'stream', type: 'Stream' },
118
- { context: ['.query', '.params', '.body'], name: 'req', type: 'Request' },
119
- { context: ['.resolve', '.reject'], name: 'promise', type: 'Promise' },
120
- { context: ['.name', '.version', '.description'], name: 'packageInfo', type: 'PackageInfo' },
121
- { context: ['.key', '.value', '.ttl'], name: 'cacheEntry', type: 'CacheEntry' },
122
- { context: ['.token', '.user', '.role'], name: 'session', type: 'Session' },
123
- { context: ['.width', '.height', '.x', '.y'], name: 'rect', type: 'Rect' },
124
- { context: ['.type', '.data', '.target'], name: 'event', type: 'Event' },
125
- { context: ['.path', '.content', '.encoding'], name: 'file', type: 'FileInfo' },
126
- { context: ['.host', '.port', '.protocol'], name: 'connectionInfo', type: 'ConnectionInfo' },
127
- ];
128
-
129
- /**
130
- * Check if a name looks minified (short, no semantic meaning).
131
- * @param {string} name
132
- * @returns {boolean}
133
- */
134
- function isMinifiedLike(name) {
135
- if (!name) return true;
136
- return /^[a-zA-Z][a-zA-Z0-9$]{0,2}$/.test(name);
137
- }
138
-
139
- /**
140
- * Score how well a pattern matches the given context.
141
- *
142
- * @param {object} pattern - from claude-code-patterns.json
143
- * @param {string[]} contextStrings - strings found near the identifier
144
- * @param {string[]} propertyNames - property accesses found near the identifier
145
- * @returns {number} 0-1 match score
146
- */
147
- function scorePatternMatch(pattern, contextStrings, propertyNames) {
148
- let score = 0;
149
- let totalChecks = 0;
150
-
151
- // Check context string matches
152
- if (pattern.context_strings && pattern.context_strings.length > 0) {
153
- for (const ctx of pattern.context_strings) {
154
- totalChecks++;
155
- if (contextStrings.some((s) => s.includes(ctx) || ctx.includes(s))) {
156
- score++;
157
- }
158
- }
159
- }
160
-
161
- // Check property name matches
162
- if (pattern.property_names && pattern.property_names.length > 0) {
163
- for (const prop of pattern.property_names) {
164
- totalChecks++;
165
- const propAccess = `.${prop}`;
166
- if (propertyNames.some((p) => p === propAccess || p === prop)) {
167
- score++;
168
- }
169
- }
170
- }
171
-
172
- if (totalChecks === 0) return 0;
173
- return score / totalChecks;
174
- }
175
-
176
- /**
177
- * Predict a meaningful name for a minified identifier.
178
- *
179
- * @param {string} minifiedName - the original short name (e.g. "s$")
180
- * @param {string[]} contextStrings - strings/properties near the identifier
181
- * @param {object} [options]
182
- * @param {string} [options.declaration] - the declaration statement
183
- * @param {string} [options.patternPath] - path to patterns JSON
184
- * @param {number} [options.minConfidence=0.3] - minimum confidence to accept
185
- * @returns {{name: string, confidence: number, source: string}|null}
186
- */
187
- function predictName(minifiedName, contextStrings, options = {}) {
188
- const { declaration = '', patternPath, minConfidence = 0.3 } = options;
189
-
190
- // Separate context strings from property accesses
191
- const props = contextStrings.filter((s) => s.startsWith('.'));
192
- const strings = contextStrings.filter((s) => !s.startsWith('.'));
193
-
194
- // 1. Try direct-assignment analysis (highest precision)
195
- // If X = Y.propertyName, then X should be named "propertyName"
196
- const directName = inferFromDirectAssignment(minifiedName, declaration);
197
- if (directName) {
198
- return { name: directName.name, confidence: directName.confidence, source: 'direct-assign' };
199
- }
200
-
201
- // 2. Try structural rules (high specificity)
202
- for (const rule of STRUCTURAL_RULES) {
203
- if (rule.test(declaration)) {
204
- const name = rule.nameFrom(declaration, contextStrings);
205
- if (name && rule.confidence >= minConfidence) {
206
- return { name, confidence: rule.confidence, source: 'structural' };
207
- }
208
- }
209
- }
210
-
211
- // 3. Try pattern matching against training data
212
- const patterns = loadPatterns(patternPath);
213
- let bestMatch = null;
214
- let bestScore = 0;
215
-
216
- for (const pattern of patterns) {
217
- const matchScore = scorePatternMatch(pattern, strings, props);
218
- const adjustedScore = matchScore * pattern.confidence;
219
-
220
- if (adjustedScore > bestScore && adjustedScore >= minConfidence) {
221
- bestScore = adjustedScore;
222
- bestMatch = pattern;
223
- }
224
- }
225
-
226
- if (bestMatch) {
227
- return {
228
- name: toCamelCase(bestMatch.inferred_name),
229
- confidence: bestScore,
230
- source: 'pattern',
231
- };
232
- }
233
-
234
- // 3. Try parameter naming rules
235
- for (const rule of PARAM_RULES) {
236
- const matches = rule.context.filter((c) =>
237
- contextStrings.some((s) => s.includes(c)),
238
- );
239
- if (matches.length >= 2) {
240
- return {
241
- name: rule.name,
242
- confidence: 0.6,
243
- source: 'param-rule',
244
- type: rule.type,
245
- };
246
- }
247
- }
248
-
249
- // 4. Heuristic fallbacks based on usage patterns
250
- const heuristic = heuristicName(minifiedName, contextStrings, declaration);
251
- if (heuristic && heuristic.confidence >= minConfidence) {
252
- return heuristic;
253
- }
254
-
255
- return null;
256
- }
257
-
258
- /**
259
- * Infer a name from direct assignment patterns.
260
- * let B = A.messages -> B should be "messages"
261
- * let Z = await Y2(B, G, ...) -> Z might be "result" or inferred from Y2
262
- * var s$ = async function*(...) -> handled by structural rules
263
- *
264
- * @param {string} minifiedName
265
- * @param {string} declaration
266
- * @returns {{name: string, confidence: number}|null}
267
- */
268
- function inferFromDirectAssignment(minifiedName, declaration) {
269
- if (!declaration) return null;
270
- const escaped = minifiedName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
271
-
272
- // Pattern: X = Y.propertyName (property extraction)
273
- const propMatch = declaration.match(
274
- new RegExp(`${escaped}\\s*=\\s*\\w+\\.(\\w{2,30})`)
275
- );
276
- if (propMatch && propMatch[1].length > 2) {
277
- return { name: propMatch[1], confidence: 0.85 };
278
- }
279
-
280
- // Pattern: X = await Y(...) (function call result)
281
- const awaitCallMatch = declaration.match(
282
- new RegExp(`${escaped}\\s*=\\s*await\\s+(\\w+)\\(`)
283
- );
284
- if (awaitCallMatch) {
285
- const fnName = awaitCallMatch[1];
286
- if (!isMinifiedLike(fnName)) {
287
- // Infer from function name: createApiRequest -> apiRequest
288
- const resultName = fnName.replace(/^(create|get|fetch|make|build|load)/, '').replace(/^./, (c) => c.toLowerCase());
289
- return { name: resultName || 'result', confidence: 0.7 };
290
- }
291
- return { name: 'result', confidence: 0.5 };
292
- }
293
-
294
- // Pattern: for await (let J of Z) -> J is an item from the iterable
295
- const forOfMatch = declaration.match(
296
- new RegExp(`for\\s+await\\s*\\(\\s*(?:let|const|var)\\s+${escaped}\\s+of\\s+(\\w+)`)
297
- );
298
- if (forOfMatch) {
299
- return { name: 'item', confidence: 0.5 };
300
- }
301
-
302
- // Pattern: function parameter (first param of function*)
303
- const funcParamMatch = declaration.match(
304
- new RegExp(`function\\s*\\*?\\s*\\w*\\s*\\(\\s*${escaped}(?:\\s*,|\\s*\\))`)
305
- );
306
- if (funcParamMatch) {
307
- // First parameter of a function -> "params" or "options"
308
- return { name: 'params', confidence: 0.5 };
309
- }
310
-
311
- return null;
312
- }
313
-
314
- /**
315
- * Heuristic name inference from usage patterns.
316
- *
317
- * @param {string} minifiedName
318
- * @param {string[]} contextStrings
319
- * @param {string} declaration
320
- * @returns {{name: string, confidence: number, source: string}|null}
321
- */
322
- function heuristicName(minifiedName, contextStrings, declaration) {
323
- const ctx = contextStrings.join(' ').toLowerCase();
324
-
325
- // Callback / handler patterns
326
- if (ctx.includes('callback') || ctx.includes('handler') || ctx.includes('listener')) {
327
- return { name: 'handler', confidence: 0.4, source: 'heuristic' };
328
- }
329
- // Error variable
330
- if (/catch\s*\(\s*$/.test(declaration) || ctx.includes('error') && ctx.includes('catch')) {
331
- return { name: 'error', confidence: 0.5, source: 'heuristic' };
332
- }
333
- // Iterator / loop variable used with .length or [i]
334
- if (ctx.includes('.length') && ctx.includes('for')) {
335
- return { name: 'items', confidence: 0.4, source: 'heuristic' };
336
- }
337
- // Result of await
338
- if (declaration.includes('await')) {
339
- if (ctx.includes('fetch') || ctx.includes('request')) {
340
- return { name: 'response', confidence: 0.5, source: 'heuristic' };
341
- }
342
- return { name: 'result', confidence: 0.35, source: 'heuristic' };
343
- }
344
- // Boolean-looking (used in conditions)
345
- if (ctx.includes('if') && (ctx.includes('===true') || ctx.includes('===false') || ctx.includes('!!'))) {
346
- return { name: 'isEnabled', confidence: 0.35, source: 'heuristic' };
347
- }
348
-
349
- return null;
350
- }
351
-
352
- /**
353
- * Convert a PascalCase or kebab-case name to camelCase.
354
- * @param {string} name
355
- * @returns {string}
356
- */
357
- function toCamelCase(name) {
358
- if (!name) return name;
359
- // If already camelCase, return as-is
360
- if (/^[a-z]/.test(name) && !name.includes('-') && !name.includes('_')) return name;
361
- // PascalCase -> camelCase
362
- if (/^[A-Z]/.test(name) && !name.includes('-') && !name.includes('_')) {
363
- return name[0].toLowerCase() + name.slice(1);
364
- }
365
- // kebab-case or snake_case -> camelCase
366
- return name.replace(/[-_](.)/g, (_, c) => c.toUpperCase()).replace(/^./, (c) => c.toLowerCase());
367
- }
368
-
369
- /**
370
- * Infer a parameter name from its position and usage.
371
- * @param {number} index - parameter position (0-based)
372
- * @param {string[]} contextStrings
373
- * @returns {string}
374
- */
375
- function inferParamName(index, contextStrings) {
376
- // Try param rules
377
- for (const rule of PARAM_RULES) {
378
- const matches = rule.context.filter((c) =>
379
- contextStrings.some((s) => s.includes(c)),
380
- );
381
- if (matches.length >= 1) {
382
- return rule.name;
383
- }
384
- }
385
-
386
- // Generic fallback
387
- const fallbacks = ['param', 'value', 'data', 'input', 'arg', 'item'];
388
- return fallbacks[index] || `param${index}`;
389
- }
390
-
391
- module.exports = {
392
- predictName,
393
- loadPatterns,
394
- scorePatternMatch,
395
- inferParamName,
396
- toCamelCase,
397
- isMinifiedLike,
398
- STRUCTURAL_RULES,
399
- PARAM_RULES,
400
- };
1
+ /**
2
+ * name-predictor.js - Pattern-based name prediction for minified identifiers.
3
+ *
4
+ * Uses the 210+ training patterns from claude-code-patterns.json to infer
5
+ * meaningful names based on context strings, property accesses, and
6
+ * structural patterns found near each identifier.
7
+ *
8
+ * Falls back to structural heuristics when no pattern matches.
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ /** Cached patterns array — loaded once. */
17
+ let _cachedPatterns = null;
18
+
19
+ /**
20
+ * Load patterns from the JSON training data.
21
+ * @param {string} [patternPath] - override path to patterns JSON
22
+ * @returns {Array<{context_strings: string[], property_names: string[], inferred_name: string, module_hint: string, confidence: number}>}
23
+ */
24
+ function loadPatterns(patternPath) {
25
+ if (_cachedPatterns && !patternPath) return _cachedPatterns;
26
+
27
+ const defaultPath = path.resolve(
28
+ __dirname,
29
+ '../../../../../crates/ruvector-decompiler/data/claude-code-patterns.json',
30
+ );
31
+ const resolved = patternPath || defaultPath;
32
+
33
+ try {
34
+ const raw = fs.readFileSync(resolved, 'utf-8');
35
+ _cachedPatterns = JSON.parse(raw);
36
+ return _cachedPatterns;
37
+ } catch {
38
+ // Pattern file not found — return empty, rely on structural rules
39
+ _cachedPatterns = [];
40
+ return _cachedPatterns;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Structural rename rules — deterministic, no model needed.
46
+ * Ordered by specificity (most specific first).
47
+ */
48
+ const STRUCTURAL_RULES = [
49
+ // Async generators
50
+ {
51
+ test: (decl) => /async\s+function\s*\*/.test(decl),
52
+ nameFrom: (decl, ctx) => {
53
+ if (ctx.some((s) => s.includes('agent')) && ctx.some((s) => s.includes('loop'))) return 'agentLoop';
54
+ if (ctx.some((s) => s.includes('stream'))) return 'streamGenerator';
55
+ return 'asyncGenerator';
56
+ },
57
+ confidence: 0.7,
58
+ },
59
+ // Error subclasses
60
+ {
61
+ test: (decl) => /class\s+\w+\s+extends\s+Error/.test(decl),
62
+ nameFrom: (decl) => {
63
+ const match = decl.match(/class\s+(\w+)\s+extends\s+(\w*Error)/);
64
+ if (match && !isMinifiedLike(match[1])) return match[1];
65
+ return 'CustomError';
66
+ },
67
+ confidence: 0.85,
68
+ },
69
+ // Class extending another class
70
+ {
71
+ test: (decl) => /class\s+\w+\s+extends\s+\w+/.test(decl),
72
+ nameFrom: (decl) => {
73
+ const match = decl.match(/class\s+(\w+)\s+extends\s+(\w+)/);
74
+ if (match && !isMinifiedLike(match[1])) return match[1];
75
+ if (match) return `${match[2]}Subclass`;
76
+ return null;
77
+ },
78
+ confidence: 0.75,
79
+ },
80
+ // Regular class
81
+ {
82
+ test: (decl) => /class\s+\w+/.test(decl),
83
+ nameFrom: (decl) => {
84
+ const match = decl.match(/class\s+(\w+)/);
85
+ if (match && !isMinifiedLike(match[1])) return match[1];
86
+ return null;
87
+ },
88
+ confidence: 0.7,
89
+ },
90
+ // Export default function
91
+ {
92
+ test: (decl) => /export\s+default\s+function/.test(decl),
93
+ nameFrom: () => 'defaultExport',
94
+ confidence: 0.5,
95
+ },
96
+ // Named function
97
+ {
98
+ test: (decl) => /function\s+([a-zA-Z_$]\w+)/.test(decl),
99
+ nameFrom: (decl) => {
100
+ const match = decl.match(/function\s+([a-zA-Z_$]\w+)/);
101
+ if (match && !isMinifiedLike(match[1])) return match[1];
102
+ return null;
103
+ },
104
+ confidence: 0.8,
105
+ },
106
+ ];
107
+
108
+ /**
109
+ * Parameter naming rules based on usage context.
110
+ */
111
+ const PARAM_RULES = [
112
+ { context: ['.messages', 'systemPrompt', 'canUseTool'], name: 'params', type: 'AgentLoopParams' },
113
+ { context: ['.method', '.url', '.headers'], name: 'request', type: 'Request' },
114
+ { context: ['.status', '.json', '.send'], name: 'response', type: 'Response' },
115
+ { context: ['.next', '.done', '.value'], name: 'iterator', type: 'Iterator' },
116
+ { context: ['.emit', '.on', '.removeListener'], name: 'emitter', type: 'EventEmitter' },
117
+ { context: ['.pipe', '.write', '.end'], name: 'stream', type: 'Stream' },
118
+ { context: ['.query', '.params', '.body'], name: 'req', type: 'Request' },
119
+ { context: ['.resolve', '.reject'], name: 'promise', type: 'Promise' },
120
+ { context: ['.name', '.version', '.description'], name: 'packageInfo', type: 'PackageInfo' },
121
+ { context: ['.key', '.value', '.ttl'], name: 'cacheEntry', type: 'CacheEntry' },
122
+ { context: ['.token', '.user', '.role'], name: 'session', type: 'Session' },
123
+ { context: ['.width', '.height', '.x', '.y'], name: 'rect', type: 'Rect' },
124
+ { context: ['.type', '.data', '.target'], name: 'event', type: 'Event' },
125
+ { context: ['.path', '.content', '.encoding'], name: 'file', type: 'FileInfo' },
126
+ { context: ['.host', '.port', '.protocol'], name: 'connectionInfo', type: 'ConnectionInfo' },
127
+ ];
128
+
129
+ /**
130
+ * Check if a name looks minified (short, no semantic meaning).
131
+ * @param {string} name
132
+ * @returns {boolean}
133
+ */
134
+ function isMinifiedLike(name) {
135
+ if (!name) return true;
136
+ return /^[a-zA-Z][a-zA-Z0-9$]{0,2}$/.test(name);
137
+ }
138
+
139
+ /**
140
+ * Score how well a pattern matches the given context.
141
+ *
142
+ * @param {object} pattern - from claude-code-patterns.json
143
+ * @param {string[]} contextStrings - strings found near the identifier
144
+ * @param {string[]} propertyNames - property accesses found near the identifier
145
+ * @returns {number} 0-1 match score
146
+ */
147
+ function scorePatternMatch(pattern, contextStrings, propertyNames) {
148
+ let score = 0;
149
+ let totalChecks = 0;
150
+
151
+ // Check context string matches
152
+ if (pattern.context_strings && pattern.context_strings.length > 0) {
153
+ for (const ctx of pattern.context_strings) {
154
+ totalChecks++;
155
+ if (contextStrings.some((s) => s.includes(ctx) || ctx.includes(s))) {
156
+ score++;
157
+ }
158
+ }
159
+ }
160
+
161
+ // Check property name matches
162
+ if (pattern.property_names && pattern.property_names.length > 0) {
163
+ for (const prop of pattern.property_names) {
164
+ totalChecks++;
165
+ const propAccess = `.${prop}`;
166
+ if (propertyNames.some((p) => p === propAccess || p === prop)) {
167
+ score++;
168
+ }
169
+ }
170
+ }
171
+
172
+ if (totalChecks === 0) return 0;
173
+ return score / totalChecks;
174
+ }
175
+
176
+ /**
177
+ * Predict a meaningful name for a minified identifier.
178
+ *
179
+ * @param {string} minifiedName - the original short name (e.g. "s$")
180
+ * @param {string[]} contextStrings - strings/properties near the identifier
181
+ * @param {object} [options]
182
+ * @param {string} [options.declaration] - the declaration statement
183
+ * @param {string} [options.patternPath] - path to patterns JSON
184
+ * @param {number} [options.minConfidence=0.3] - minimum confidence to accept
185
+ * @returns {{name: string, confidence: number, source: string}|null}
186
+ */
187
+ function predictName(minifiedName, contextStrings, options = {}) {
188
+ const { declaration = '', patternPath, minConfidence = 0.3 } = options;
189
+
190
+ // Separate context strings from property accesses
191
+ const props = contextStrings.filter((s) => s.startsWith('.'));
192
+ const strings = contextStrings.filter((s) => !s.startsWith('.'));
193
+
194
+ // 1. Try direct-assignment analysis (highest precision)
195
+ // If X = Y.propertyName, then X should be named "propertyName"
196
+ const directName = inferFromDirectAssignment(minifiedName, declaration);
197
+ if (directName) {
198
+ return { name: directName.name, confidence: directName.confidence, source: 'direct-assign' };
199
+ }
200
+
201
+ // 2. Try structural rules (high specificity)
202
+ for (const rule of STRUCTURAL_RULES) {
203
+ if (rule.test(declaration)) {
204
+ const name = rule.nameFrom(declaration, contextStrings);
205
+ if (name && rule.confidence >= minConfidence) {
206
+ return { name, confidence: rule.confidence, source: 'structural' };
207
+ }
208
+ }
209
+ }
210
+
211
+ // 3. Try pattern matching against training data
212
+ const patterns = loadPatterns(patternPath);
213
+ let bestMatch = null;
214
+ let bestScore = 0;
215
+
216
+ for (const pattern of patterns) {
217
+ const matchScore = scorePatternMatch(pattern, strings, props);
218
+ const adjustedScore = matchScore * pattern.confidence;
219
+
220
+ if (adjustedScore > bestScore && adjustedScore >= minConfidence) {
221
+ bestScore = adjustedScore;
222
+ bestMatch = pattern;
223
+ }
224
+ }
225
+
226
+ if (bestMatch) {
227
+ return {
228
+ name: toCamelCase(bestMatch.inferred_name),
229
+ confidence: bestScore,
230
+ source: 'pattern',
231
+ };
232
+ }
233
+
234
+ // 3. Try parameter naming rules
235
+ for (const rule of PARAM_RULES) {
236
+ const matches = rule.context.filter((c) =>
237
+ contextStrings.some((s) => s.includes(c)),
238
+ );
239
+ if (matches.length >= 2) {
240
+ return {
241
+ name: rule.name,
242
+ confidence: 0.6,
243
+ source: 'param-rule',
244
+ type: rule.type,
245
+ };
246
+ }
247
+ }
248
+
249
+ // 4. Heuristic fallbacks based on usage patterns
250
+ const heuristic = heuristicName(minifiedName, contextStrings, declaration);
251
+ if (heuristic && heuristic.confidence >= minConfidence) {
252
+ return heuristic;
253
+ }
254
+
255
+ return null;
256
+ }
257
+
258
+ /**
259
+ * Infer a name from direct assignment patterns.
260
+ * let B = A.messages -> B should be "messages"
261
+ * let Z = await Y2(B, G, ...) -> Z might be "result" or inferred from Y2
262
+ * var s$ = async function*(...) -> handled by structural rules
263
+ *
264
+ * @param {string} minifiedName
265
+ * @param {string} declaration
266
+ * @returns {{name: string, confidence: number}|null}
267
+ */
268
+ function inferFromDirectAssignment(minifiedName, declaration) {
269
+ if (!declaration) return null;
270
+ const escaped = minifiedName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
271
+
272
+ // Pattern: X = Y.propertyName (property extraction)
273
+ const propMatch = declaration.match(
274
+ new RegExp(`${escaped}\\s*=\\s*\\w+\\.(\\w{2,30})`)
275
+ );
276
+ if (propMatch && propMatch[1].length > 2) {
277
+ return { name: propMatch[1], confidence: 0.85 };
278
+ }
279
+
280
+ // Pattern: X = await Y(...) (function call result)
281
+ const awaitCallMatch = declaration.match(
282
+ new RegExp(`${escaped}\\s*=\\s*await\\s+(\\w+)\\(`)
283
+ );
284
+ if (awaitCallMatch) {
285
+ const fnName = awaitCallMatch[1];
286
+ if (!isMinifiedLike(fnName)) {
287
+ // Infer from function name: createApiRequest -> apiRequest
288
+ const resultName = fnName.replace(/^(create|get|fetch|make|build|load)/, '').replace(/^./, (c) => c.toLowerCase());
289
+ return { name: resultName || 'result', confidence: 0.7 };
290
+ }
291
+ return { name: 'result', confidence: 0.5 };
292
+ }
293
+
294
+ // Pattern: for await (let J of Z) -> J is an item from the iterable
295
+ const forOfMatch = declaration.match(
296
+ new RegExp(`for\\s+await\\s*\\(\\s*(?:let|const|var)\\s+${escaped}\\s+of\\s+(\\w+)`)
297
+ );
298
+ if (forOfMatch) {
299
+ return { name: 'item', confidence: 0.5 };
300
+ }
301
+
302
+ // Pattern: function parameter (first param of function*)
303
+ const funcParamMatch = declaration.match(
304
+ new RegExp(`function\\s*\\*?\\s*\\w*\\s*\\(\\s*${escaped}(?:\\s*,|\\s*\\))`)
305
+ );
306
+ if (funcParamMatch) {
307
+ // First parameter of a function -> "params" or "options"
308
+ return { name: 'params', confidence: 0.5 };
309
+ }
310
+
311
+ return null;
312
+ }
313
+
314
+ /**
315
+ * Heuristic name inference from usage patterns.
316
+ *
317
+ * @param {string} minifiedName
318
+ * @param {string[]} contextStrings
319
+ * @param {string} declaration
320
+ * @returns {{name: string, confidence: number, source: string}|null}
321
+ */
322
+ function heuristicName(minifiedName, contextStrings, declaration) {
323
+ const ctx = contextStrings.join(' ').toLowerCase();
324
+
325
+ // Callback / handler patterns
326
+ if (ctx.includes('callback') || ctx.includes('handler') || ctx.includes('listener')) {
327
+ return { name: 'handler', confidence: 0.4, source: 'heuristic' };
328
+ }
329
+ // Error variable
330
+ if (/catch\s*\(\s*$/.test(declaration) || ctx.includes('error') && ctx.includes('catch')) {
331
+ return { name: 'error', confidence: 0.5, source: 'heuristic' };
332
+ }
333
+ // Iterator / loop variable used with .length or [i]
334
+ if (ctx.includes('.length') && ctx.includes('for')) {
335
+ return { name: 'items', confidence: 0.4, source: 'heuristic' };
336
+ }
337
+ // Result of await
338
+ if (declaration.includes('await')) {
339
+ if (ctx.includes('fetch') || ctx.includes('request')) {
340
+ return { name: 'response', confidence: 0.5, source: 'heuristic' };
341
+ }
342
+ return { name: 'result', confidence: 0.35, source: 'heuristic' };
343
+ }
344
+ // Boolean-looking (used in conditions)
345
+ if (ctx.includes('if') && (ctx.includes('===true') || ctx.includes('===false') || ctx.includes('!!'))) {
346
+ return { name: 'isEnabled', confidence: 0.35, source: 'heuristic' };
347
+ }
348
+
349
+ return null;
350
+ }
351
+
352
+ /**
353
+ * Convert a PascalCase or kebab-case name to camelCase.
354
+ * @param {string} name
355
+ * @returns {string}
356
+ */
357
+ function toCamelCase(name) {
358
+ if (!name) return name;
359
+ // If already camelCase, return as-is
360
+ if (/^[a-z]/.test(name) && !name.includes('-') && !name.includes('_')) return name;
361
+ // PascalCase -> camelCase
362
+ if (/^[A-Z]/.test(name) && !name.includes('-') && !name.includes('_')) {
363
+ return name[0].toLowerCase() + name.slice(1);
364
+ }
365
+ // kebab-case or snake_case -> camelCase
366
+ return name.replace(/[-_](.)/g, (_, c) => c.toUpperCase()).replace(/^./, (c) => c.toLowerCase());
367
+ }
368
+
369
+ /**
370
+ * Infer a parameter name from its position and usage.
371
+ * @param {number} index - parameter position (0-based)
372
+ * @param {string[]} contextStrings
373
+ * @returns {string}
374
+ */
375
+ function inferParamName(index, contextStrings) {
376
+ // Try param rules
377
+ for (const rule of PARAM_RULES) {
378
+ const matches = rule.context.filter((c) =>
379
+ contextStrings.some((s) => s.includes(c)),
380
+ );
381
+ if (matches.length >= 1) {
382
+ return rule.name;
383
+ }
384
+ }
385
+
386
+ // Generic fallback
387
+ const fallbacks = ['param', 'value', 'data', 'input', 'arg', 'item'];
388
+ return fallbacks[index] || `param${index}`;
389
+ }
390
+
391
+ module.exports = {
392
+ predictName,
393
+ loadPatterns,
394
+ scorePatternMatch,
395
+ inferParamName,
396
+ toCamelCase,
397
+ isMinifiedLike,
398
+ STRUCTURAL_RULES,
399
+ PARAM_RULES,
400
+ };