@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.
- package/README.md +4 -4
- package/package.json +2 -1
- package/src/analysis/type-flow.ts +1 -1
- package/src/cache/incremental-cache.ts +86 -80
- package/src/contract/contract-reader.ts +1 -0
- package/src/contract/lock-compiler.ts +95 -13
- package/src/contract/schema.ts +2 -0
- package/src/error-handler.ts +2 -1
- package/src/graph/cluster-detector.ts +2 -4
- package/src/graph/dead-code-detector.ts +303 -117
- package/src/graph/graph-builder.ts +21 -161
- package/src/graph/impact-analyzer.ts +1 -0
- package/src/graph/index.ts +2 -0
- package/src/graph/rich-function-index.ts +1080 -0
- package/src/graph/symbol-table.ts +252 -0
- package/src/hash/hash-store.ts +1 -0
- package/src/index.ts +2 -0
- package/src/parser/base-extractor.ts +19 -0
- package/src/parser/boundary-checker.ts +31 -12
- package/src/parser/error-recovery.ts +5 -4
- package/src/parser/function-body-extractor.ts +248 -0
- package/src/parser/go/go-extractor.ts +249 -676
- package/src/parser/index.ts +132 -318
- package/src/parser/language-registry.ts +57 -0
- package/src/parser/oxc-parser.ts +166 -28
- package/src/parser/oxc-resolver.ts +179 -11
- package/src/parser/parser-constants.ts +1 -0
- package/src/parser/rust/rust-extractor.ts +109 -0
- package/src/parser/tree-sitter/parser.ts +369 -62
- package/src/parser/tree-sitter/queries.ts +106 -10
- package/src/parser/types.ts +20 -1
- package/src/search/bm25.ts +21 -8
- package/src/search/direct-search.ts +472 -0
- package/src/search/embedding-provider.ts +249 -0
- package/src/search/index.ts +12 -0
- package/src/search/semantic-search.ts +435 -0
- package/src/utils/artifact-transaction.ts +1 -0
- package/src/utils/atomic-write.ts +1 -0
- package/src/utils/errors.ts +89 -4
- package/src/utils/fs.ts +104 -50
- package/src/utils/json.ts +1 -0
- package/src/utils/language-registry.ts +84 -6
- package/src/utils/path.ts +26 -0
- package/tests/dead-code.test.ts +3 -2
- package/tests/direct-search.test.ts +435 -0
- package/tests/error-recovery.test.ts +143 -0
- package/tests/fixtures/simple-api/src/index.ts +1 -1
- package/tests/go-parser.test.ts +19 -335
- package/tests/js-parser.test.ts +18 -1089
- package/tests/language-registry-all.test.ts +276 -0
- package/tests/language-registry.test.ts +6 -4
- package/tests/parse-diagnostics.test.ts +9 -96
- package/tests/parser.test.ts +42 -771
- package/tests/polyglot-parser.test.ts +117 -0
- package/tests/rich-function-index.test.ts +703 -0
- package/tests/tree-sitter-parser.test.ts +108 -80
- package/tests/ts-parser.test.ts +8 -8
- package/tests/verification.test.ts +175 -0
- package/src/parser/base-parser.ts +0 -16
- package/src/parser/go/go-parser.ts +0 -43
- package/src/parser/javascript/js-extractor.ts +0 -278
- package/src/parser/javascript/js-parser.ts +0 -101
- package/src/parser/typescript/ts-extractor.ts +0 -447
- 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,
|
|
2
|
+
import type { DependencyGraph, GraphEdge } from './types.js'
|
|
3
3
|
import type { MikkLock } from '../contract/schema.js'
|
|
4
|
-
import type { ParsedFile, ParsedFunction, ParsedClass,
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
364
|
-
|
|
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:
|
|
368
|
-
type:
|
|
369
|
-
confidence:
|
|
370
|
-
weight:
|
|
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
|
package/src/graph/index.ts
CHANGED
|
@@ -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
|
|