@getmikk/core 2.0.14 → 2.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +4 -4
  2. package/package.json +2 -1
  3. package/src/analysis/type-flow.ts +1 -1
  4. package/src/cache/incremental-cache.ts +86 -80
  5. package/src/contract/contract-reader.ts +1 -0
  6. package/src/contract/lock-compiler.ts +95 -13
  7. package/src/contract/schema.ts +2 -0
  8. package/src/error-handler.ts +2 -1
  9. package/src/graph/cluster-detector.ts +2 -4
  10. package/src/graph/dead-code-detector.ts +303 -117
  11. package/src/graph/graph-builder.ts +21 -161
  12. package/src/graph/impact-analyzer.ts +1 -0
  13. package/src/graph/index.ts +2 -0
  14. package/src/graph/rich-function-index.ts +1080 -0
  15. package/src/graph/symbol-table.ts +252 -0
  16. package/src/hash/hash-store.ts +1 -0
  17. package/src/index.ts +2 -0
  18. package/src/parser/base-extractor.ts +19 -0
  19. package/src/parser/boundary-checker.ts +31 -12
  20. package/src/parser/error-recovery.ts +5 -4
  21. package/src/parser/function-body-extractor.ts +248 -0
  22. package/src/parser/go/go-extractor.ts +249 -676
  23. package/src/parser/index.ts +132 -318
  24. package/src/parser/language-registry.ts +57 -0
  25. package/src/parser/oxc-parser.ts +166 -28
  26. package/src/parser/oxc-resolver.ts +179 -11
  27. package/src/parser/parser-constants.ts +1 -0
  28. package/src/parser/rust/rust-extractor.ts +109 -0
  29. package/src/parser/tree-sitter/parser.ts +369 -62
  30. package/src/parser/tree-sitter/queries.ts +106 -10
  31. package/src/parser/types.ts +20 -1
  32. package/src/search/bm25.ts +21 -8
  33. package/src/search/direct-search.ts +472 -0
  34. package/src/search/embedding-provider.ts +249 -0
  35. package/src/search/index.ts +12 -0
  36. package/src/search/semantic-search.ts +435 -0
  37. package/src/utils/artifact-transaction.ts +1 -0
  38. package/src/utils/atomic-write.ts +1 -0
  39. package/src/utils/errors.ts +89 -4
  40. package/src/utils/fs.ts +104 -50
  41. package/src/utils/json.ts +1 -0
  42. package/src/utils/language-registry.ts +84 -6
  43. package/src/utils/path.ts +26 -0
  44. package/tests/dead-code.test.ts +3 -2
  45. package/tests/direct-search.test.ts +435 -0
  46. package/tests/error-recovery.test.ts +143 -0
  47. package/tests/fixtures/simple-api/src/index.ts +1 -1
  48. package/tests/go-parser.test.ts +19 -335
  49. package/tests/js-parser.test.ts +18 -1089
  50. package/tests/language-registry-all.test.ts +276 -0
  51. package/tests/language-registry.test.ts +6 -4
  52. package/tests/parse-diagnostics.test.ts +9 -96
  53. package/tests/parser.test.ts +42 -771
  54. package/tests/polyglot-parser.test.ts +117 -0
  55. package/tests/rich-function-index.test.ts +703 -0
  56. package/tests/tree-sitter-parser.test.ts +108 -80
  57. package/tests/ts-parser.test.ts +8 -8
  58. package/tests/verification.test.ts +175 -0
  59. package/src/parser/base-parser.ts +0 -16
  60. package/src/parser/go/go-parser.ts +0 -43
  61. package/src/parser/javascript/js-extractor.ts +0 -278
  62. package/src/parser/javascript/js-parser.ts +0 -101
  63. package/src/parser/typescript/ts-extractor.ts +0 -447
  64. package/src/parser/typescript/ts-parser.ts +0 -36
@@ -0,0 +1,472 @@
1
+ import type { RichFunction, RichParam, RichCall, SearchQuery, SearchResult, ContextRequest, FunctionContext } from '../graph/rich-function-index.js'
2
+ import { RichFunctionIndex } from '../graph/rich-function-index.js'
3
+ import type { MikkLock } from '../contract/schema.js'
4
+ import type { DependencyGraph } from '../graph/types.js'
5
+
6
+ export interface DirectQuery {
7
+ find?: string
8
+ name?: string
9
+ exact?: string
10
+ startsWith?: string
11
+ contains?: string
12
+ file?: string
13
+ module?: string
14
+ type?: 'function' | 'method' | 'class' | 'all'
15
+ exported?: boolean
16
+ async?: boolean
17
+ returns?: string
18
+ param?: string
19
+ paramType?: string
20
+ decorator?: string
21
+ keyword?: string
22
+ calls?: string
23
+ calledBy?: string
24
+ usage?: string
25
+ }
26
+
27
+ export interface DirectContext {
28
+ signature?: string
29
+ params?: string
30
+ returnType?: string
31
+ purpose?: string
32
+ body?: boolean | number
33
+ calls?: boolean
34
+ callers?: boolean
35
+ related?: boolean | number
36
+ full?: boolean
37
+ }
38
+
39
+ export class DirectSearchEngine {
40
+ private index: RichFunctionIndex
41
+ private lock: MikkLock
42
+ private graph?: DependencyGraph
43
+
44
+ constructor(lock: MikkLock, graph?: DependencyGraph) {
45
+ this.lock = lock
46
+ this.graph = graph
47
+ this.index = new RichFunctionIndex()
48
+ this.index.index(lock, graph)
49
+ }
50
+
51
+ reindex(lock: MikkLock, graph?: DependencyGraph): void {
52
+ this.lock = lock
53
+ this.graph = graph
54
+ this.index.index(lock, graph)
55
+ }
56
+
57
+ find(query: string): RichFunction | undefined {
58
+ const exact = this.index.getByExactName(query)
59
+ if (exact) return exact
60
+
61
+ const results = this.index.searchByName(query, 1)
62
+ if (results.length > 0) return results[0].function
63
+
64
+ return undefined
65
+ }
66
+
67
+ findBySignature(signature: string): RichFunction | undefined {
68
+ return this.index.findBySignature(signature)
69
+ }
70
+
71
+ findBySignatureAndParams(signature: string, paramTypes?: string[]): RichFunction | undefined {
72
+ return this.index.findBySignatureAndParams(signature, paramTypes)
73
+ }
74
+
75
+ findByLocation(file: string, line: number): RichFunction | undefined {
76
+ return this.index.findByLocation(file, line)
77
+ }
78
+
79
+ findAll(query: string): RichFunction[] {
80
+ return this.index.searchByName(query, 100).map((r: SearchResult) => r.function)
81
+ }
82
+
83
+ search(query: DirectQuery): RichFunction[] {
84
+ const searchQuery: SearchQuery = {}
85
+
86
+ if (query.name) searchQuery.nameContains = query.name
87
+ if (query.exact) searchQuery.exactName = query.exact
88
+ if (query.startsWith) searchQuery.nameStartsWith = query.startsWith
89
+ if (query.contains) searchQuery.nameContains = query.contains
90
+ if (query.file) searchQuery.inFile = query.file
91
+ if (query.module) searchQuery.moduleId = query.module
92
+ if (query.exported !== undefined) searchQuery.isExported = query.exported
93
+ if (query.async !== undefined) searchQuery.isAsync = query.async
94
+ if (query.returns) searchQuery.returnTypeContains = query.returns
95
+ if (query.param) searchQuery.hasParam = query.param
96
+ if (query.paramType) searchQuery.paramTypes = [query.paramType]
97
+ if (query.decorator) searchQuery.hasDecorator = query.decorator
98
+ if (query.keyword) searchQuery.keyword = query.keyword
99
+ if (query.calls) searchQuery.calls = query.calls
100
+ if (query.calledBy) searchQuery.calledBy = query.calledBy
101
+
102
+ return this.index.search(searchQuery).map((r: SearchResult) => r.function)
103
+ }
104
+
105
+ findInFile(file: string): RichFunction[] {
106
+ return this.index.getByFile(file)
107
+ }
108
+
109
+ findInModule(moduleId: string): RichFunction[] {
110
+ return this.index.getByModule(moduleId)
111
+ }
112
+
113
+ findExport(pattern?: string): RichFunction[] {
114
+ const exported = this.index.getExported()
115
+ if (!pattern) return exported
116
+
117
+ const lower = pattern.toLowerCase()
118
+ return exported.filter((f: RichFunction) =>
119
+ f.name.toLowerCase().includes(lower) ||
120
+ f.purpose.toLowerCase().includes(lower)
121
+ )
122
+ }
123
+
124
+ findAsync(pattern?: string): RichFunction[] {
125
+ const asyncFns = this.index.search({ isAsync: true, limit: 1000 })
126
+ .map((r: SearchResult) => r.function)
127
+
128
+ if (!pattern) return asyncFns
129
+
130
+ const lower = pattern.toLowerCase()
131
+ return asyncFns.filter((f: RichFunction) =>
132
+ f.name.toLowerCase().includes(lower) ||
133
+ f.purpose.toLowerCase().includes(lower)
134
+ )
135
+ }
136
+
137
+ findByReturnType(returnType: string): RichFunction[] {
138
+ return this.index.search({ returnTypeContains: returnType, limit: 100 })
139
+ .map((r: SearchResult) => r.function)
140
+ }
141
+
142
+ findByParamTypes(paramTypes: string[]): RichFunction[] {
143
+ return this.index.findByParamTypes(paramTypes)
144
+ }
145
+
146
+ findByKeyword(keyword: string): RichFunction[] {
147
+ return this.index.search({ keyword, limit: 100 })
148
+ .map((r: SearchResult) => r.function)
149
+ }
150
+
151
+ findCallers(functionId: string): RichFunction[] {
152
+ return this.index.getCallers(functionId)
153
+ }
154
+
155
+ findCallees(functionId: string): RichFunction[] {
156
+ return this.index.getCallees(functionId)
157
+ }
158
+
159
+ findRelated(functionId: string, depth: number = 1): RichFunction[] {
160
+ return this.index.getRelated(functionId, depth)
161
+ }
162
+
163
+ getContext(functionId: string, options?: DirectContext): FunctionContext | null {
164
+ const include = options?.full ? 'all' :
165
+ options?.body ? 'body' :
166
+ options?.calls ? 'calls' : 'full'
167
+
168
+ return this.index.getContext({
169
+ functionId,
170
+ include,
171
+ maxBodyLines: typeof options?.body === 'number' ? options.body : undefined,
172
+ })
173
+ }
174
+
175
+ getSignature(functionId: string): string | undefined {
176
+ const fn = this.index.get(functionId)
177
+ return fn?.fullSignature
178
+ }
179
+
180
+ getSignatures(functionIds: string[]): Record<string, string> {
181
+ return this.index.getSignaturesMap(functionIds)
182
+ }
183
+
184
+ getSummaries(functionIds: string[]): Array<{ id: string; name: string; signature: string; purpose: string; file: string }> {
185
+ return this.index.getSummaries(functionIds)
186
+ }
187
+
188
+ getAllSummaries(): Array<{ id: string; name: string; signature: string; purpose: string; file: string }> {
189
+ return this.index.getAllSummaries()
190
+ }
191
+
192
+ getStats() {
193
+ return this.index.getStats()
194
+ }
195
+
196
+ getAllKeywords(): string[] {
197
+ return this.index.getKeywords()
198
+ }
199
+
200
+ getExactMatch(name: string): RichFunction | undefined {
201
+ return this.index.getByExactName(name)
202
+ }
203
+
204
+ getById(id: string): RichFunction | undefined {
205
+ return this.index.get(id)
206
+ }
207
+
208
+ count(): number {
209
+ return this.index.getCount()
210
+ }
211
+
212
+ quickSearch(text: string, limit: number = 10): RichFunction[] {
213
+ return this.index.searchText(text, limit).map((r: SearchResult) => r.function)
214
+ }
215
+
216
+ getFunctionWithContext(functionId: string, _includeBodies: boolean = false): {
217
+ id: string
218
+ name: string
219
+ signature: string
220
+ fullSignature: string
221
+ params: Array<{ name: string; type: string; optional: boolean }>
222
+ returnType: string
223
+ purpose: string
224
+ file: string
225
+ startLine: number
226
+ endLine: number
227
+ isExported: boolean
228
+ isAsync: boolean
229
+ calls: Array<{ name: string; type: string }>
230
+ calledBy: string[]
231
+ keywords: string[]
232
+ decorators: string[]
233
+ } | null {
234
+ const fn = this.index.get(functionId)
235
+ if (!fn) return null
236
+
237
+ return {
238
+ id: fn.id,
239
+ name: fn.name,
240
+ signature: fn.signature,
241
+ fullSignature: fn.fullSignature,
242
+ params: fn.params.map((p: RichParam) => ({ name: p.name, type: p.type, optional: p.optional })),
243
+ returnType: fn.returnType,
244
+ purpose: fn.purpose,
245
+ file: fn.file,
246
+ startLine: fn.startLine,
247
+ endLine: fn.endLine,
248
+ isExported: fn.isExported,
249
+ isAsync: fn.isAsync,
250
+ calls: fn.calls.map((c: RichCall) => ({ name: c.name, type: c.type })),
251
+ calledBy: fn.calledBy,
252
+ keywords: fn.keywords,
253
+ decorators: fn.decorators,
254
+ }
255
+ }
256
+
257
+ getFunctionSignatures(functionIds: string[]): Map<string, string> {
258
+ const map = new Map<string, string>()
259
+ for (const id of functionIds) {
260
+ const fn = this.index.get(id)
261
+ if (fn) {
262
+ map.set(id, fn.fullSignature)
263
+ }
264
+ }
265
+ return map
266
+ }
267
+
268
+ exportAll(): Array<{
269
+ id: string
270
+ name: string
271
+ signature: string
272
+ fullSignature: string
273
+ file: string
274
+ moduleId: string
275
+ returnType: string
276
+ isExported: boolean
277
+ isAsync: boolean
278
+ paramCount: number
279
+ purpose: string
280
+ keywords: string[]
281
+ }> {
282
+ return this.index.getAll().map((fn: RichFunction) => ({
283
+ id: fn.id,
284
+ name: fn.name,
285
+ signature: fn.signature,
286
+ fullSignature: fn.fullSignature,
287
+ file: fn.file,
288
+ moduleId: fn.moduleId,
289
+ returnType: fn.returnType,
290
+ isExported: fn.isExported,
291
+ isAsync: fn.isAsync,
292
+ paramCount: fn.params.length,
293
+ purpose: fn.purpose,
294
+ keywords: fn.keywords,
295
+ }))
296
+ }
297
+
298
+ searchByPattern(pattern: string): RichFunction[] {
299
+ const regex = new RegExp(pattern, 'i')
300
+ return [...this.index.getAll()].filter((fn: RichFunction) => {
301
+ if (regex.test(fn.name)) return true
302
+ if (regex.test(fn.purpose)) return true
303
+ if (regex.test(fn.file)) return true
304
+ if (regex.test(fn.returnType)) return true
305
+ return false
306
+ })
307
+ }
308
+
309
+ getByParamType(type: string): RichFunction[] {
310
+ return this.index.search({ paramTypes: [type], limit: 100 })
311
+ .map((r: SearchResult) => r.function)
312
+ }
313
+
314
+ getByDecorator(decorator: string): RichFunction[] {
315
+ return this.index.search({ hasDecorator: decorator, limit: 100 })
316
+ .map((r: SearchResult) => r.function)
317
+ }
318
+
319
+ findBySignaturePattern(pattern: string): RichFunction[] {
320
+ const lowerPattern = pattern.toLowerCase()
321
+ return [...this.index.getAll()].filter((fn: RichFunction) => {
322
+ const sig = fn.fullSignature.toLowerCase()
323
+ return sig.includes(lowerPattern)
324
+ })
325
+ }
326
+
327
+ findSimilar(query: {
328
+ name?: string
329
+ signature?: string
330
+ paramTypes?: string[]
331
+ returnType?: string
332
+ file?: string
333
+ calls?: string[]
334
+ }): RichFunction[] {
335
+ const candidates = [...this.index.getAll()]
336
+ const scored: Array<{ fn: RichFunction; score: number }> = []
337
+
338
+ for (const fn of candidates) {
339
+ let score = 0
340
+
341
+ if (query.signature && fn.fullSignature === query.signature) {
342
+ score += 100
343
+ }
344
+
345
+ if (query.name && this.levenshteinSimilar(fn.name, query.name) > 0.7) {
346
+ score += 50
347
+ }
348
+
349
+ if (query.paramTypes && query.paramTypes.length > 0) {
350
+ const fnParamTypes = fn.params.map(p => p.type)
351
+ const matchCount = query.paramTypes.filter(pt =>
352
+ fnParamTypes.some(fpt => fpt.includes(pt))
353
+ ).length
354
+ score += (matchCount / query.paramTypes.length) * 30
355
+ }
356
+
357
+ if (query.returnType && fn.returnType.includes(query.returnType)) {
358
+ score += 20
359
+ }
360
+
361
+ if (query.file && fn.file.includes(query.file)) {
362
+ score += 15
363
+ }
364
+
365
+ if (query.calls && query.calls.length > 0) {
366
+ const fnCalls = fn.calls.map(c => c.name)
367
+ const matchCount = query.calls.filter(c => fnCalls.includes(c)).length
368
+ score += (matchCount / query.calls.length) * 20
369
+ }
370
+
371
+ if (score > 0) {
372
+ scored.push({ fn, score })
373
+ }
374
+ }
375
+
376
+ scored.sort((a, b) => b.score - a.score)
377
+ return scored.map(s => s.fn)
378
+ }
379
+
380
+ findByFileAndSimilarity(file: string, name: string, limit: number = 5): RichFunction[] {
381
+ const fnsInFile = this.index.getByFile(file)
382
+
383
+ const scored = fnsInFile.map(fn => {
384
+ const similarity = this.levenshteinSimilar(fn.name, name)
385
+ return { fn, similarity }
386
+ })
387
+
388
+ scored.sort((a, b) => b.similarity - a.similarity)
389
+ return scored.slice(0, limit).map(s => s.fn)
390
+ }
391
+
392
+ private levenshteinSimilar(a: string, b: string): number {
393
+ const maxLen = Math.max(a.length, b.length)
394
+ if (maxLen === 0) return 1
395
+
396
+ const distance = this.levenshtein(a.toLowerCase(), b.toLowerCase())
397
+ return 1 - (distance / maxLen)
398
+ }
399
+
400
+ private levenshtein(a: string, b: string): number {
401
+ if (a.length === 0) return b.length
402
+ if (b.length === 0) return a.length
403
+
404
+ const matrix: number[][] = []
405
+ for (let i = 0; i <= b.length; i++) {
406
+ matrix[i] = [i]
407
+ }
408
+ for (let j = 0; j <= a.length; j++) {
409
+ matrix[0][j] = j
410
+ }
411
+
412
+ for (let i = 1; i <= b.length; i++) {
413
+ for (let j = 1; j <= a.length; j++) {
414
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
415
+ matrix[i][j] = matrix[i - 1][j - 1]
416
+ } else {
417
+ matrix[i][j] = Math.min(
418
+ matrix[i - 1][j - 1] + 1,
419
+ matrix[i][j - 1] + 1,
420
+ matrix[i - 1][j] + 1
421
+ )
422
+ }
423
+ }
424
+ }
425
+
426
+ return matrix[b.length][a.length]
427
+ }
428
+ }
429
+
430
+ export function createDirectSearch(lock: MikkLock, graph?: DependencyGraph): DirectSearchEngine {
431
+ return new DirectSearchEngine(lock, graph)
432
+ }
433
+
434
+ export function extractSignatures(functions: RichFunction[]): string[] {
435
+ return functions.map((f: RichFunction) => f.fullSignature)
436
+ }
437
+
438
+ export function extractNames(functions: RichFunction[]): string[] {
439
+ return functions.map((f: RichFunction) => f.name)
440
+ }
441
+
442
+ export function extractSignaturesMap(functions: RichFunction[]): Record<string, string> {
443
+ const map: Record<string, string> = {}
444
+ for (const fn of functions) {
445
+ map[fn.id] = fn.fullSignature
446
+ }
447
+ return map
448
+ }
449
+
450
+ export function summarizeFunction(fn: RichFunction): string {
451
+ const parts = [fn.fullSignature]
452
+ if (fn.purpose) parts.push(`- ${fn.purpose}`)
453
+ if (fn.calls.length > 0) {
454
+ parts.push(`- calls: ${fn.calls.slice(0, 5).map((c: RichCall) => c.name).join(', ')}${fn.calls.length > 5 ? '...' : ''}`)
455
+ }
456
+ if (fn.calledBy.length > 0) {
457
+ parts.push(`- called by: ${fn.calledBy.length} function(s)`)
458
+ }
459
+ return parts.join('\n')
460
+ }
461
+
462
+ export function formatFunctionList(functions: RichFunction[], includePurpose: boolean = false): string {
463
+ if (functions.length === 0) return '(none)'
464
+
465
+ return functions.map((fn: RichFunction) => {
466
+ const line = `${fn.fullSignature} (${fn.file.split('/').pop()})`
467
+ if (includePurpose && fn.purpose) {
468
+ return `${line}\n Purpose: ${fn.purpose}`
469
+ }
470
+ return line
471
+ }).join('\n')
472
+ }