@getmikk/core 2.0.14 → 2.0.15

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
@@ -1,7 +1,9 @@
1
1
  import * as nodePath from 'node:path'
2
- import type { DependencyGraph, GraphNode, GraphEdge } from './types.js'
2
+ import type { DependencyGraph, GraphEdge } from './types.js'
3
3
  import type { MikkLock } from '../contract/schema.js'
4
- import type { ParsedFile, ParsedFunction, ParsedClass, ParsedVariable, ParsedGeneric } from '../parser/types.js'
4
+ import type { ParsedFile, ParsedFunction, ParsedClass, ParsedGeneric, ParsedVariable } from '../parser/types.js'
5
+ import { GlobalSymbolTable } from './symbol-table.js'
6
+ import { normalizePathQuiet } from '../utils/path.js'
5
7
 
6
8
  export const EDGE_WEIGHTS = {
7
9
  imports: 1.0,
@@ -14,25 +16,19 @@ export const EDGE_WEIGHTS = {
14
16
 
15
17
  /**
16
18
  * GraphBuilder — three-pass dependency graph construction.
17
- *
18
- * ID contract (must match oxc-parser.ts exactly):
19
- * function: fn:<absolute-posix-path>:<FunctionName>
20
- * class: class:<absolute-posix-path>:<ClassName>
21
- * type/enum: type:<absolute-posix-path>:<Name> | enum:<absolute-posix-path>:<Name>
22
- * variable: var:<absolute-posix-path>:<Name>
23
- * file: <absolute-posix-path> (no prefix)
24
- *
25
- * No case normalisation — paths and names keep their original case.
26
- * Lookups use exact string matching after posix-normalising separators.
19
+ * Now integrated with GlobalSymbolTable for high-precision resolution.
27
20
  */
28
21
  export class GraphBuilder {
29
22
  build(files: ParsedFile[]): DependencyGraph {
30
23
  const graph = this.createEmptyGraph()
31
24
  const edgeKeys = new Set<string>()
25
+ const symbolTable = new GlobalSymbolTable()
32
26
 
33
- // Pass 1: Register all nodes
27
+ // Pass 1: Register all nodes & symbols
34
28
  for (const file of files) {
35
29
  this.addFileNode(graph, file)
30
+ symbolTable.register(file)
31
+
36
32
  for (const fn of file.functions) this.addFunctionNode(graph, fn)
37
33
  for (const cls of file.classes ?? []) this.addClassNode(graph, cls)
38
34
  for (const gen of file.generics ?? []) this.addGenericNode(graph, gen)
@@ -46,19 +42,15 @@ export class GraphBuilder {
46
42
  this.addInheritanceEdges(graph, file, edgeKeys)
47
43
  }
48
44
 
49
- // Pass 3: Behavioural edges (calls, accesses)
45
+ // Pass 3: Behavioural edges (Using Global Symbol Table)
50
46
  for (const file of files) {
51
- this.addCallEdges(graph, file, edgeKeys)
47
+ this.addCallEdges(graph, file, symbolTable, edgeKeys)
52
48
  }
53
49
 
54
50
  this.buildAdjacencyMaps(graph)
55
51
  return graph
56
52
  }
57
53
 
58
- /**
59
- * Rebuild a lightweight DependencyGraph from a serialised lock file.
60
- * Used for preflight checks without re-parsing the codebase.
61
- */
62
54
  buildFromLock(lock: MikkLock): DependencyGraph {
63
55
  const graph = this.createEmptyGraph()
64
56
  const edgeKeys = new Set<string>()
@@ -94,7 +86,6 @@ export class GraphBuilder {
94
86
  // Edges from lock data
95
87
  for (const file of Object.values(lock.files)) {
96
88
  const fp = this.normPath(file.path)
97
- // Import edges
98
89
  for (const imp of file.imports ?? []) {
99
90
  if (!imp.resolvedPath) continue
100
91
  const rp = this.normPath(imp.resolvedPath)
@@ -102,7 +93,6 @@ export class GraphBuilder {
102
93
  this.pushEdge(graph, edgeKeys, { from: fp, to: rp, type: 'imports', confidence: 1.0, weight: EDGE_WEIGHTS.imports })
103
94
  }
104
95
  }
105
- // Containment: file → functions in this file
106
96
  for (const fn of Object.values(lock.functions)) {
107
97
  if (this.normPath(fn.file) === fp) {
108
98
  this.pushEdge(graph, edgeKeys, { from: fp, to: fn.id, type: 'contains', confidence: 1.0, weight: EDGE_WEIGHTS.contains })
@@ -110,7 +100,6 @@ export class GraphBuilder {
110
100
  }
111
101
  }
112
102
 
113
- // Call edges
114
103
  for (const fn of Object.values(lock.functions)) {
115
104
  for (const calleeId of fn.calls) {
116
105
  if (graph.nodes.has(calleeId)) {
@@ -123,22 +112,14 @@ export class GraphBuilder {
123
112
  return graph
124
113
  }
125
114
 
126
- // -------------------------------------------------------------------------
127
- // Helpers
128
- // -------------------------------------------------------------------------
129
-
130
115
  private normPath(p: string): string {
131
- return p.replace(/\\/g, '/').toLowerCase()
116
+ return normalizePathQuiet(p)
132
117
  }
133
118
 
134
119
  private createEmptyGraph(): DependencyGraph {
135
120
  return { nodes: new Map(), edges: [], outEdges: new Map(), inEdges: new Map() }
136
121
  }
137
122
 
138
- // -------------------------------------------------------------------------
139
- // Pass 1: Node Registration
140
- // -------------------------------------------------------------------------
141
-
142
123
  private addFileNode(graph: DependencyGraph, file: ParsedFile): void {
143
124
  const p = this.normPath(file.path)
144
125
  graph.nodes.set(p, {
@@ -158,7 +139,7 @@ export class GraphBuilder {
158
139
  params: fn.params, returnType: fn.returnType !== 'void' ? fn.returnType : undefined,
159
140
  edgeCasesHandled: fn.edgeCasesHandled,
160
141
  errorHandling: fn.errorHandling,
161
- detailedLines: fn.detailedLines,
142
+ detailedLines: fn.detailedLines
162
143
  },
163
144
  })
164
145
  }
@@ -185,7 +166,6 @@ export class GraphBuilder {
185
166
  metadata: {
186
167
  startLine: gen.startLine, endLine: gen.endLine,
187
168
  isExported: gen.isExported, purpose: gen.purpose,
188
- // Store the kind (interface/type/enum) in genericKind, NOT hash
189
169
  genericKind: gen.type,
190
170
  },
191
171
  })
@@ -199,10 +179,6 @@ export class GraphBuilder {
199
179
  })
200
180
  }
201
181
 
202
- // -------------------------------------------------------------------------
203
- // Pass 2: Structural Edges
204
- // -------------------------------------------------------------------------
205
-
206
182
  private addImportEdges(graph: DependencyGraph, file: ParsedFile, edgeKeys: Set<string>): void {
207
183
  const src = this.normPath(file.path)
208
184
  for (const imp of file.imports) {
@@ -227,16 +203,9 @@ export class GraphBuilder {
227
203
  this.pushEdge(graph, edgeKeys, { from: cls.id, to: prop.id, type: 'contains', confidence: 1.0, weight: EDGE_WEIGHTS.contains })
228
204
  }
229
205
  }
230
- for (const gen of file.generics ?? []) {
231
- this.pushEdge(graph, edgeKeys, { from: src, to: gen.id, type: 'contains', confidence: 1.0, weight: EDGE_WEIGHTS.contains })
232
- }
233
- for (const v of file.variables ?? []) {
234
- this.pushEdge(graph, edgeKeys, { from: src, to: v.id, type: 'contains', confidence: 1.0, weight: EDGE_WEIGHTS.contains })
235
- }
236
206
  }
237
207
 
238
208
  private addInheritanceEdges(graph: DependencyGraph, file: ParsedFile, edgeKeys: Set<string>): void {
239
- // Build class name → id map from graph (already populated in Pass 1)
240
209
  const classNameToId = new Map<string, string>()
241
210
  for (const [, node] of graph.nodes) {
242
211
  if (node.type === 'class') classNameToId.set(node.name, node.id)
@@ -260,64 +229,12 @@ export class GraphBuilder {
260
229
  }
261
230
  }
262
231
 
263
- // -------------------------------------------------------------------------
264
- // Pass 3: Behavioural Edges (calls, accesses)
265
- //
266
- // Call resolution order (priority-first):
267
- // 1. Named import: `import { foo } from './x'` → fn:<resolvedPath>:foo
268
- // 2. Default import alias: `import jwt from './x'; jwt.verify()` → fn:<resolvedPath>:verify
269
- // 3. Local function exact: same file, same name
270
- // 4. Local class method: SomeClass.method in same file
271
- //
272
- // Confidence levels:
273
- // 1.0 — local exact match (AST confirmed)
274
- // 0.8 — named import match
275
- // 0.6 — default-import method match
276
- // 0.5 — dotted name stripped to simple name (receiver uncertain)
277
- // -------------------------------------------------------------------------
278
-
279
- private addCallEdges(graph: DependencyGraph, file: ParsedFile, edgeKeys: Set<string>): void {
232
+ private addCallEdges(graph: DependencyGraph, file: ParsedFile, symbolTable: GlobalSymbolTable, edgeKeys: Set<string>): void {
280
233
  const filePath = this.normPath(file.path)
281
234
 
282
- // Build import lookup tables for this file
283
- // key: the local name used in code (e.g. "verifyToken")
284
- // value: the canonical graph node ID (e.g. "fn:/abs/path/jwt.ts:verifyToken")
285
- const namedImportIds = new Map<string, string>()
286
- // key: local alias (e.g. "jwt"), value: resolved absolute file path
287
- const defaultImportPaths = new Map<string, string>()
288
-
289
- for (const imp of file.imports) {
290
- if (!imp.resolvedPath) continue
291
- const resolvedPath = this.normPath(imp.resolvedPath)
292
-
293
- for (const name of imp.names) {
294
- const localName = name.toLowerCase()
295
- if (imp.isDefault) {
296
- // `import jwt from './jwt'` → defaultImportPaths['jwt'] = '/abs/.../jwt.ts'
297
- defaultImportPaths.set(localName, resolvedPath)
298
- } else {
299
- // `import { verifyToken } from './jwt'` → namedImportIds['verifyToken'] = 'fn:.../jwt.ts:verifytoken'
300
- const candidateId = `fn:${resolvedPath}:${localName}`
301
- if (graph.nodes.has(candidateId)) {
302
- namedImportIds.set(localName, candidateId)
303
- }
304
- }
305
- }
306
- }
307
-
308
- // Class name → class node ID for local method resolution
309
- const localClassIds = new Map<string, string>()
310
- for (const cls of file.classes ?? []) {
311
- localClassIds.set(cls.name, cls.id)
312
- }
313
-
314
- // Collect all (sourceId, callName) pairs from every function and method
315
235
  const behaviors: Array<{ sourceId: string; calls: Array<{ name: string; type: string }> }> = [
316
- // Module-level calls
317
236
  { sourceId: filePath, calls: file.calls ?? [] },
318
- // Function-level calls
319
237
  ...file.functions.map(fn => ({ sourceId: fn.id, calls: fn.calls })),
320
- // Class method calls
321
238
  ...(file.classes ?? []).flatMap(cls =>
322
239
  cls.methods.map(m => ({ sourceId: m.id, calls: m.calls }))
323
240
  ),
@@ -325,78 +242,21 @@ export class GraphBuilder {
325
242
 
326
243
  for (const { sourceId, calls } of behaviors) {
327
244
  for (const call of calls) {
328
- const callName = call.name
329
- if (!callName || callName === 'super') continue
330
-
331
- const normalizedCallName = callName.toLowerCase()
332
- const hasDot = normalizedCallName.includes('.')
333
- const simpleName = hasDot ? normalizedCallName.split('.').pop()! : normalizedCallName
334
- const receiver = hasDot ? normalizedCallName.split('.')[0] : null
335
- const isPropertyAccess = call.type === 'property'
336
-
337
- // ── 1. Named import exact match ──────────────────────────
338
- // Try full name first (e.g. "jwt.verify" mapped via named import of "verify")
339
- const namedId = namedImportIds.get(normalizedCallName) ?? (receiver === null ? namedImportIds.get(simpleName) : undefined)
340
- if (namedId) {
341
- this.pushEdge(graph, edgeKeys, {
342
- from: sourceId, to: namedId,
343
- type: isPropertyAccess ? 'accesses' : 'calls',
344
- confidence: 0.8, weight: EDGE_WEIGHTS.calls.exact,
345
- })
346
- continue
347
- }
348
-
349
- // ── 2. Default import: receiver is the alias ─────────────
350
- if (receiver && defaultImportPaths.has(receiver)) {
351
- const resolvedFile = defaultImportPaths.get(receiver)!
352
- const methodId = `fn:${resolvedFile}:${simpleName}`
353
- if (graph.nodes.has(methodId)) {
354
- this.pushEdge(graph, edgeKeys, {
355
- from: sourceId, to: methodId,
356
- type: isPropertyAccess ? 'accesses' : 'calls',
357
- confidence: 0.6, weight: EDGE_WEIGHTS.calls.method,
358
- })
359
- continue
360
- }
361
- }
245
+ if (!call.name || call.name === 'super') continue
362
246
 
363
- // ── 3. Local function exact match ────────────────────────
364
- const localId = `fn:${filePath}:${simpleName}`
365
- if (graph.nodes.has(localId) && localId !== sourceId) {
247
+ const targetId = symbolTable.resolve(call.name, filePath, file.imports)
248
+ if (targetId && targetId !== sourceId) {
366
249
  this.pushEdge(graph, edgeKeys, {
367
- from: sourceId, to: localId,
368
- type: isPropertyAccess ? 'accesses' : 'calls',
369
- confidence: simpleName === callName ? 1.0 : 0.5,
370
- weight: simpleName === callName ? EDGE_WEIGHTS.calls.exact : EDGE_WEIGHTS.calls.fuzzy,
250
+ from: sourceId, to: targetId,
251
+ type: call.type === 'property' ? 'accesses' : 'calls',
252
+ confidence: 0.9,
253
+ weight: call.type === 'property' ? EDGE_WEIGHTS.accesses : EDGE_WEIGHTS.calls.exact,
371
254
  })
372
- continue
373
255
  }
374
-
375
- // ── 4. Local class method: SomeClass.method ──────────────
376
- if (receiver && localClassIds.has(receiver)) {
377
- // Method IDs are stored as fn:<file>:<ClassName>.<methodName>
378
- const classMethodId = `fn:${filePath}:${receiver}.${simpleName}`
379
- if (graph.nodes.has(classMethodId) && classMethodId !== sourceId) {
380
- this.pushEdge(graph, edgeKeys, {
381
- from: sourceId, to: classMethodId,
382
- type: isPropertyAccess ? 'accesses' : 'calls',
383
- confidence: 0.8, weight: EDGE_WEIGHTS.calls.method,
384
- })
385
- continue
386
- }
387
- }
388
-
389
- // Unresolved call — intentionally not added to graph.
390
- // Callers can detect incomplete coverage by comparing
391
- // fn.calls.length vs outgoing 'calls' edge count on fn.id.
392
256
  }
393
257
  }
394
258
  }
395
259
 
396
- // -------------------------------------------------------------------------
397
- // Edge helpers
398
- // -------------------------------------------------------------------------
399
-
400
260
  private pushEdge(graph: DependencyGraph, edgeKeys: Set<string>, edge: GraphEdge): void {
401
261
  const key = `${edge.from}->${edge.to}:${edge.type}`
402
262
  if (edgeKeys.has(key)) return
@@ -1,3 +1,4 @@
1
+
1
2
  import type { DependencyGraph, ImpactResult, ClassifiedImpact } from './types.js'
2
3
  import { RiskEngine } from './risk-engine.js'
3
4
  import { ConfidenceEngine } from './confidence-engine.js'
@@ -8,4 +8,6 @@ export { RiskEngine } from './risk-engine.js'
8
8
  export type { RiskContext, RiskModifiers } from './risk-engine.js'
9
9
  export { ConfidenceEngine } from './confidence-engine.js'
10
10
  export { QueryEngine } from './query-engine.js'
11
+ export { RichFunctionIndex } from './rich-function-index.js'
12
+ export type { RichFunction, RichParam, RichCall, RichErrorHandling, SearchQuery, SearchResult, ContextRequest, FunctionContext } from './rich-function-index.js'
11
13