@getmikk/core 2.0.13 → 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 (71) hide show
  1. package/README.md +4 -4
  2. package/package.json +2 -1
  3. package/src/analysis/index.ts +9 -0
  4. package/src/analysis/taint-analysis.ts +419 -0
  5. package/src/analysis/type-flow.ts +247 -0
  6. package/src/cache/incremental-cache.ts +278 -0
  7. package/src/cache/index.ts +1 -0
  8. package/src/contract/contract-generator.ts +31 -3
  9. package/src/contract/contract-reader.ts +1 -0
  10. package/src/contract/lock-compiler.ts +125 -12
  11. package/src/contract/schema.ts +4 -0
  12. package/src/error-handler.ts +2 -1
  13. package/src/graph/cluster-detector.ts +2 -4
  14. package/src/graph/dead-code-detector.ts +303 -117
  15. package/src/graph/graph-builder.ts +21 -161
  16. package/src/graph/impact-analyzer.ts +1 -0
  17. package/src/graph/index.ts +2 -0
  18. package/src/graph/rich-function-index.ts +1080 -0
  19. package/src/graph/symbol-table.ts +252 -0
  20. package/src/hash/hash-store.ts +1 -0
  21. package/src/index.ts +4 -0
  22. package/src/parser/base-extractor.ts +19 -0
  23. package/src/parser/boundary-checker.ts +31 -12
  24. package/src/parser/error-recovery.ts +647 -0
  25. package/src/parser/function-body-extractor.ts +248 -0
  26. package/src/parser/go/go-extractor.ts +249 -676
  27. package/src/parser/index.ts +138 -295
  28. package/src/parser/language-registry.ts +57 -0
  29. package/src/parser/oxc-parser.ts +166 -28
  30. package/src/parser/oxc-resolver.ts +179 -11
  31. package/src/parser/parser-constants.ts +1 -0
  32. package/src/parser/rust/rust-extractor.ts +109 -0
  33. package/src/parser/tree-sitter/parser.ts +400 -66
  34. package/src/parser/tree-sitter/queries.ts +106 -10
  35. package/src/parser/types.ts +20 -1
  36. package/src/search/bm25.ts +21 -8
  37. package/src/search/direct-search.ts +472 -0
  38. package/src/search/embedding-provider.ts +249 -0
  39. package/src/search/index.ts +12 -0
  40. package/src/search/semantic-search.ts +435 -0
  41. package/src/security/index.ts +1 -0
  42. package/src/security/scanner.ts +342 -0
  43. package/src/utils/artifact-transaction.ts +1 -0
  44. package/src/utils/atomic-write.ts +1 -0
  45. package/src/utils/errors.ts +89 -4
  46. package/src/utils/fs.ts +150 -65
  47. package/src/utils/json.ts +1 -0
  48. package/src/utils/language-registry.ts +96 -5
  49. package/src/utils/minimatch.ts +49 -6
  50. package/src/utils/path.ts +26 -0
  51. package/tests/dead-code.test.ts +3 -2
  52. package/tests/direct-search.test.ts +435 -0
  53. package/tests/error-recovery.test.ts +143 -0
  54. package/tests/fixtures/simple-api/src/index.ts +1 -1
  55. package/tests/go-parser.test.ts +19 -335
  56. package/tests/js-parser.test.ts +18 -1089
  57. package/tests/language-registry-all.test.ts +276 -0
  58. package/tests/language-registry.test.ts +6 -4
  59. package/tests/parse-diagnostics.test.ts +9 -96
  60. package/tests/parser.test.ts +42 -771
  61. package/tests/polyglot-parser.test.ts +117 -0
  62. package/tests/rich-function-index.test.ts +703 -0
  63. package/tests/tree-sitter-parser.test.ts +108 -80
  64. package/tests/ts-parser.test.ts +8 -8
  65. package/tests/verification.test.ts +175 -0
  66. package/src/parser/base-parser.ts +0 -16
  67. package/src/parser/go/go-parser.ts +0 -43
  68. package/src/parser/javascript/js-extractor.ts +0 -278
  69. package/src/parser/javascript/js-parser.ts +0 -101
  70. package/src/parser/typescript/ts-extractor.ts +0 -447
  71. package/src/parser/typescript/ts-parser.ts +0 -36
package/README.md CHANGED
@@ -75,10 +75,10 @@ One root hash comparison = instant full drift detection. Persisted in SQLite wit
75
75
 
76
76
  Compiles a `DependencyGraph` + `MikkContract` + parsed files into a `MikkLock`. The lock file is the single source of truth for all MCP tools and CLI commands.
77
77
 
78
- Lock format v1.7.0:
79
- - Integer-based function index (`fnIndex`) — call graph edges stored as integer references, not repeated strings
80
- - Compact JSON output — no pretty-printing
81
- - Backward-compatible hydration for older formats
78
+ Lock format:
79
+ - Integer-based function index (`fnIndex`) — call graph edges stored as integer references
80
+ - Compact JSON output
81
+ - Backward-compatible hydration
82
82
 
83
83
  ### ContractReader / ContractWriter / LockReader
84
84
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getmikk/core",
3
- "version": "2.0.13",
3
+ "version": "2.0.15",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
@@ -33,6 +33,7 @@
33
33
  "eslint": "^9.39.2"
34
34
  },
35
35
  "dependencies": {
36
+ "@google/generative-ai": "^0.21.0",
36
37
  "better-sqlite3": "^12.6.2",
37
38
  "fast-glob": "^3.3.0",
38
39
  "tree-sitter-wasms": "^0.1.13",
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Analysis modules - Type Flow and Taint Analysis for semantic code understanding
3
+ */
4
+
5
+ export { TypeFlowAnalyzer } from './type-flow.js'
6
+ export type { TypeFlowInfo, TypeParam, TypeEdge, TypeFlowResult } from './type-flow.js'
7
+
8
+ export { TaintAnalyzer } from './taint-analysis.js'
9
+ export type { TaintSource, TaintSink, TaintFlow, DataFlowResult } from './taint-analysis.js'
@@ -0,0 +1,419 @@
1
+ /**
2
+ * Data Flow & Taint Analysis — tracks data propagation through code
3
+ * for security vulnerability detection.
4
+ */
5
+
6
+ import type { MikkLock, MikkLockFunction } from '../contract/schema.js'
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Types
10
+ // ---------------------------------------------------------------------------
11
+
12
+ export interface TaintSource {
13
+ name: string
14
+ description: string
15
+ severity: 'critical' | 'high' | 'medium' | 'low'
16
+ patterns: RegExp[]
17
+ }
18
+
19
+ export interface TaintSink {
20
+ name: string
21
+ description: string
22
+ severity: 'critical' | 'high' | 'medium' | 'low'
23
+ patterns: RegExp[]
24
+ sanitizers: string[]
25
+ }
26
+
27
+ export interface TaintFlow {
28
+ source: string
29
+ sink: string
30
+ path: string[]
31
+ vulnerability: string
32
+ severity: 'critical' | 'high' | 'medium' | 'low'
33
+ confidence: number
34
+ }
35
+
36
+ export interface DataFlowResult {
37
+ flows: TaintFlow[]
38
+ summary: {
39
+ totalFlows: number
40
+ critical: number
41
+ high: number
42
+ medium: number
43
+ low: number
44
+ }
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Default Taint Sources and Sinks
49
+ // ---------------------------------------------------------------------------
50
+
51
+ const DEFAULT_TAINT_SOURCES: TaintSource[] = [
52
+ {
53
+ name: 'user-input',
54
+ description: 'User-controlled input',
55
+ severity: 'high',
56
+ patterns: [
57
+ /req\.(body|query|params|headers)/i,
58
+ /request\.(body|query|params|headers)/i,
59
+ /input\(/i,
60
+ /process\.env/i,
61
+ /process\.argv/i,
62
+ /\bstdin\b/i,
63
+ /readline\(/i,
64
+ /readFile\(/i,
65
+ /fetch\(/i,
66
+ /axios\(/i,
67
+ /http\.request\(/i,
68
+ ],
69
+ },
70
+ {
71
+ name: 'filesystem',
72
+ description: 'File system input',
73
+ severity: 'medium',
74
+ patterns: [
75
+ /readFile\(/i,
76
+ /readFileSync\(/i,
77
+ /readdir\(/i,
78
+ /createReadStream\(/i,
79
+ /fs\.readFile\(/i,
80
+ ],
81
+ },
82
+ {
83
+ name: 'database',
84
+ description: 'Database query results',
85
+ severity: 'medium',
86
+ patterns: [
87
+ /query\(/i,
88
+ /\.find\(/i,
89
+ /\.select\(/i,
90
+ /execute\(/i,
91
+ /\.fetch\(/i,
92
+ ],
93
+ },
94
+ {
95
+ name: 'network',
96
+ description: 'Network/API responses',
97
+ severity: 'medium',
98
+ patterns: [
99
+ /fetch\(/i,
100
+ /axios\(/i,
101
+ /http\.get\(/i,
102
+ /https\.get\(/i,
103
+ /request\(/i,
104
+ /\.json\(\)/i,
105
+ ],
106
+ },
107
+ ]
108
+
109
+ const DEFAULT_TAINT_SINKS: TaintSink[] = [
110
+ {
111
+ name: 'sql-query',
112
+ description: 'SQL query execution',
113
+ severity: 'critical',
114
+ patterns: [
115
+ /execute\s*\(/i,
116
+ /query\s*\(/i,
117
+ /\.exec\(/i,
118
+ /cursor\.execute\(/i,
119
+ /db\.query\(/i,
120
+ ],
121
+ sanitizers: ['escape', 'sanitize', 'param', 'bind', 'prepare'],
122
+ },
123
+ {
124
+ name: 'command-injection',
125
+ description: 'OS command execution',
126
+ severity: 'critical',
127
+ patterns: [
128
+ /exec\s*\(/i,
129
+ /spawn\s*\(/i,
130
+ /execSync\s*\(/i,
131
+ /system\s*\(/i,
132
+ /popen\s*\(/i,
133
+ /child_process\./i,
134
+ ],
135
+ sanitizers: ['execFile', 'spawnSync', 'execFileSync'],
136
+ },
137
+ {
138
+ name: 'code-execution',
139
+ description: 'Dynamic code execution',
140
+ severity: 'critical',
141
+ patterns: [
142
+ /\beval\s*\(/i,
143
+ /\bFunction\s*\(/i,
144
+ /setTimeout\s*\(\s*\w+\s*,/i,
145
+ /setInterval\s*\(\s*\w+\s*,/i,
146
+ ],
147
+ sanitizers: [],
148
+ },
149
+ {
150
+ name: 'path-traversal',
151
+ description: 'File system operations',
152
+ severity: 'high',
153
+ patterns: [
154
+ /readFile\(/i,
155
+ /writeFile\(/i,
156
+ /open\(/i,
157
+ /createReadStream\(/i,
158
+ /stat\(/i,
159
+ /lstat\(/i,
160
+ /access\(/i,
161
+ /exists\(/i,
162
+ ],
163
+ sanitizers: ['normalize', 'resolve', 'basename', 'dirname', 'join'],
164
+ },
165
+ {
166
+ name: 'xss',
167
+ description: 'HTML/JS injection',
168
+ severity: 'high',
169
+ patterns: [
170
+ /\.innerHTML\s*=/i,
171
+ /\.outerHTML\s*=/i,
172
+ /dangerouslySetInnerHTML/i,
173
+ /document\.write\(/i,
174
+ /\.html\s*\(/i,
175
+ ],
176
+ sanitizers: ['escape', 'sanitize', 'text', 'encode', 'DOMPurify'],
177
+ },
178
+ {
179
+ name: 'prototype-pollution',
180
+ description: 'Object prototype manipulation',
181
+ severity: 'high',
182
+ patterns: [
183
+ /\[\s*['"]__proto__['"]\s*\]/i,
184
+ /\[\s*['"]constructor['"]\s*\]/i,
185
+ /Object\.assign\s*\(\s*\w+\s*,\s*\w+\s*\)/i,
186
+ ],
187
+ sanitizers: [],
188
+ },
189
+ ]
190
+
191
+ // ---------------------------------------------------------------------------
192
+ // Taint Analyzer
193
+ // ---------------------------------------------------------------------------
194
+
195
+ export class TaintAnalyzer {
196
+ private lock: MikkLock
197
+ private sources: TaintSource[]
198
+ private sinks: TaintSink[]
199
+
200
+ constructor(
201
+ lock: MikkLock,
202
+ sources?: TaintSource[],
203
+ sinks?: TaintSink[]
204
+ ) {
205
+ this.lock = lock
206
+ this.sources = sources || DEFAULT_TAINT_SOURCES
207
+ this.sinks = sinks || DEFAULT_TAINT_SINKS
208
+ }
209
+
210
+ /**
211
+ * Analyze the codebase for taint flows.
212
+ */
213
+ analyze(): DataFlowResult {
214
+ const flows: TaintFlow[] = []
215
+ const allFunctions = Object.values(this.lock.functions)
216
+
217
+ // Find functions that contain taint sources
218
+ const sourceFunctions = this.findTaintSources(allFunctions)
219
+
220
+ // Find functions that contain taint sinks
221
+ const sinkFunctions = this.findTaintSinks(allFunctions)
222
+
223
+ // Trace taint flows through call graph
224
+ for (const sourceFn of sourceFunctions) {
225
+ for (const sinkFn of sinkFunctions) {
226
+ if (sourceFn.id === sinkFn.fn.id) continue
227
+
228
+ const flow = this.traceTaintFlow(sourceFn, sinkFn.fn, sinkFn.sink, allFunctions)
229
+ if (flow) {
230
+ flows.push(flow)
231
+ }
232
+ }
233
+ }
234
+
235
+ return {
236
+ flows,
237
+ summary: {
238
+ totalFlows: flows.length,
239
+ critical: flows.filter(f => f.severity === 'critical').length,
240
+ high: flows.filter(f => f.severity === 'high').length,
241
+ medium: flows.filter(f => f.severity === 'medium').length,
242
+ low: flows.filter(f => f.severity === 'low').length,
243
+ },
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Find functions that contain taint sources.
249
+ */
250
+ private findTaintSources(functions: MikkLockFunction[]): MikkLockFunction[] {
251
+ const sources: MikkLockFunction[] = []
252
+
253
+ for (const fn of functions) {
254
+ const fnText = `${fn.name} ${fn.purpose || ''}`.toLowerCase()
255
+
256
+ for (const source of this.sources) {
257
+ for (const pattern of source.patterns) {
258
+ if (pattern.test(fnText)) {
259
+ sources.push(fn)
260
+ break
261
+ }
262
+ }
263
+ }
264
+ }
265
+
266
+ return sources
267
+ }
268
+
269
+ /**
270
+ * Find functions that contain taint sinks.
271
+ */
272
+ private findTaintSinks(functions: MikkLockFunction[]): Array<{ fn: MikkLockFunction; sink: TaintSink }> {
273
+ const sinks: Array<{ fn: MikkLockFunction; sink: TaintSink }> = []
274
+
275
+ for (const fn of functions) {
276
+ const fnText = `${fn.name} ${fn.purpose || ''}`.toLowerCase()
277
+
278
+ for (const sink of this.sinks) {
279
+ for (const pattern of sink.patterns) {
280
+ if (pattern.test(fnText)) {
281
+ sinks.push({ fn, sink })
282
+ break
283
+ }
284
+ }
285
+ }
286
+ }
287
+
288
+ return sinks
289
+ }
290
+
291
+ /**
292
+ * Trace taint flow from source to sink through call graph.
293
+ */
294
+ private traceTaintFlow(
295
+ source: MikkLockFunction,
296
+ sinkFn: MikkLockFunction,
297
+ sink: TaintSink,
298
+ allFunctions: MikkLockFunction[]
299
+ ): TaintFlow | null {
300
+ // Direct call: source calls sink directly
301
+ if (source.calls?.includes(sinkFn.id)) {
302
+ return {
303
+ source: source.name,
304
+ sink: sinkFn.name,
305
+ path: [source.name, sinkFn.name],
306
+ vulnerability: `${source.name} -> ${sinkFn.name}`,
307
+ severity: sink.severity,
308
+ confidence: 0.9,
309
+ }
310
+ }
311
+
312
+ // Check if there's a path through the call graph
313
+ const path = this.findPath(source.id, sinkFn.id, allFunctions)
314
+ if (path) {
315
+ return {
316
+ source: source.name,
317
+ sink: sinkFn.name,
318
+ path: path.map(id => this.lock.functions[id]?.name || id),
319
+ vulnerability: `${source.name} -> ${path.length - 1} intermediate(s) -> ${sinkFn.name}`,
320
+ severity: sink.severity,
321
+ confidence: 0.7,
322
+ }
323
+ }
324
+
325
+ return null
326
+ }
327
+
328
+ /**
329
+ * Find path from source to sink through call graph.
330
+ */
331
+ private findPath(
332
+ sourceId: string,
333
+ sinkId: string,
334
+ allFunctions: MikkLockFunction[]
335
+ ): string[] | null {
336
+ const visited = new Set<string>()
337
+ const path: string[] = []
338
+
339
+ function dfs(currentId: string): boolean {
340
+ if (currentId === sinkId) {
341
+ path.push(currentId)
342
+ return true
343
+ }
344
+
345
+ if (visited.has(currentId)) return false
346
+ visited.add(currentId)
347
+ path.push(currentId)
348
+
349
+ const fn = allFunctions.find(f => f.id === currentId)
350
+ if (fn?.calls) {
351
+ for (const calleeId of fn.calls) {
352
+ if (dfs(calleeId)) return true
353
+ }
354
+ }
355
+
356
+ path.pop()
357
+ return false
358
+ }
359
+
360
+ if (dfs(sourceId)) {
361
+ return path
362
+ }
363
+
364
+ return null
365
+ }
366
+
367
+ /**
368
+ * Check if a function has sanitizers that mitigate taint.
369
+ */
370
+ private hasSanitizer(fn: MikkLockFunction, sink: TaintSink): boolean {
371
+ const fnText = `${fn.name} ${fn.purpose || ''}`.toLowerCase()
372
+
373
+ for (const sanitizer of sink.sanitizers) {
374
+ if (fnText.includes(sanitizer.toLowerCase())) {
375
+ return true
376
+ }
377
+ }
378
+
379
+ return false
380
+ }
381
+
382
+ /**
383
+ * Get security findings from taint analysis.
384
+ */
385
+ getFindings(): Array<{
386
+ severity: string
387
+ title: string
388
+ file: string
389
+ line: number
390
+ description: string
391
+ }> {
392
+ const result = this.analyze()
393
+ const findings: Array<{
394
+ severity: string
395
+ title: string
396
+ file: string
397
+ line: number
398
+ description: string
399
+ }> = []
400
+
401
+ for (const flow of result.flows) {
402
+ const sourceFn = Object.values(this.lock.functions).find(
403
+ f => f.name === flow.source
404
+ )
405
+
406
+ if (sourceFn) {
407
+ findings.push({
408
+ severity: flow.severity,
409
+ title: `Potential ${flow.sink} vulnerability`,
410
+ file: sourceFn.file,
411
+ line: sourceFn.startLine,
412
+ description: `Tainted data from ${flow.source} flows to ${flow.sink} via: ${flow.path.join(' -> ')}`,
413
+ })
414
+ }
415
+ }
416
+
417
+ return findings
418
+ }
419
+ }
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Type Flow Analysis — tracks type propagation through function calls
3
+ * and provides type-aware code understanding beyond syntactic parsing.
4
+ */
5
+
6
+ import type { MikkLock, MikkLockFunction } from '../contract/schema.js'
7
+ import type { DependencyGraph } from '../graph/types.js'
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Types
11
+ // ---------------------------------------------------------------------------
12
+
13
+ export interface TypeFlowInfo {
14
+ /** Function ID */
15
+ functionId: string
16
+ /** Inferred parameter types */
17
+ paramTypes: TypeParam[]
18
+ /** Inferred return type */
19
+ returnType: string
20
+ /** Types that flow into this function from callers */
21
+ incomingTypes: TypeEdge[]
22
+ /** Types that flow out to callees */
23
+ outgoingTypes: TypeEdge[]
24
+ /** Type confidence score (0-1) */
25
+ confidence: number
26
+ }
27
+
28
+ export interface TypeParam {
29
+ name: string
30
+ type: string
31
+ source: 'annotation' | 'inference' | 'usage'
32
+ confidence: number
33
+ }
34
+
35
+ export interface TypeEdge {
36
+ from: string
37
+ to: string
38
+ type: string
39
+ confidence: number
40
+ }
41
+
42
+ export interface TypeFlowResult {
43
+ flows: Map<string, TypeFlowInfo>
44
+ summary: {
45
+ totalFunctions: number
46
+ typedFunctions: number
47
+ inferredFunctions: number
48
+ averageConfidence: number
49
+ }
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Type Flow Analyzer
54
+ // ---------------------------------------------------------------------------
55
+
56
+ export class TypeFlowAnalyzer {
57
+ private lock: MikkLock
58
+ private graph: DependencyGraph
59
+
60
+ constructor(lock: MikkLock, graph: DependencyGraph) {
61
+ this.lock = lock
62
+ this.graph = graph
63
+ }
64
+
65
+ /**
66
+ * Analyze type flow across the entire codebase.
67
+ */
68
+ analyze(): TypeFlowResult {
69
+ const flows = new Map<string, TypeFlowInfo>()
70
+ const allFunctions = Object.values(this.lock.functions)
71
+
72
+ // Phase 1: Extract explicit type annotations
73
+ for (const fn of allFunctions) {
74
+ flows.set(fn.id, this.extractExplicitTypes(fn))
75
+ }
76
+
77
+ // Phase 2: Propagate types through call graph
78
+ this.propagateTypes(flows)
79
+
80
+ // Phase 3: Compute summary statistics
81
+ const summary = this.computeSummary(flows)
82
+
83
+ return { flows, summary }
84
+ }
85
+
86
+ /**
87
+ * Get type flow for a specific function.
88
+ */
89
+ getFunctionFlow(functionId: string): TypeFlowInfo | null {
90
+ const result = this.analyze()
91
+ return result.flows.get(functionId) ?? null
92
+ }
93
+
94
+ /**
95
+ * Find all functions that return a specific type.
96
+ */
97
+ findFunctionsByReturnType(typeName: string): MikkLockFunction[] {
98
+ const result = this.analyze()
99
+ const matches: MikkLockFunction[] = []
100
+
101
+ for (const [fnId, flow] of result.flows) {
102
+ if (flow.returnType.toLowerCase().includes(typeName.toLowerCase())) {
103
+ const fn = this.lock.functions[fnId]
104
+ if (fn) matches.push(fn)
105
+ }
106
+ }
107
+
108
+ return matches
109
+ }
110
+
111
+ /**
112
+ * Find all functions that accept a specific parameter type.
113
+ */
114
+ findFunctionsByParamType(typeName: string): MikkLockFunction[] {
115
+ const result = this.analyze()
116
+ const matches: MikkLockFunction[] = []
117
+
118
+ for (const [fnId, flow] of result.flows) {
119
+ for (const param of flow.paramTypes) {
120
+ if (param.type.toLowerCase().includes(typeName.toLowerCase())) {
121
+ const fn = this.lock.functions[fnId]
122
+ if (fn) matches.push(fn)
123
+ break
124
+ }
125
+ }
126
+ }
127
+
128
+ return matches
129
+ }
130
+
131
+ /**
132
+ * Extract explicit type annotations from function metadata.
133
+ */
134
+ private extractExplicitTypes(fn: MikkLockFunction): TypeFlowInfo {
135
+ const paramTypes: TypeParam[] = []
136
+
137
+ if (fn.params) {
138
+ for (const param of fn.params) {
139
+ paramTypes.push({
140
+ name: param.name,
141
+ type: param.type || 'unknown',
142
+ source: param.type ? 'annotation' : 'inference',
143
+ confidence: param.type ? 1.0 : 0.3,
144
+ })
145
+ }
146
+ }
147
+
148
+ return {
149
+ functionId: fn.id,
150
+ paramTypes,
151
+ returnType: fn.returnType || 'unknown',
152
+ incomingTypes: [],
153
+ outgoingTypes: [],
154
+ confidence: this.computeConfidence(paramTypes, fn.returnType),
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Propagate types through the call graph.
160
+ */
161
+ private propagateTypes(flows: Map<string, TypeFlowInfo>): void {
162
+ // Build type propagation edges
163
+ for (const [fnId, flow] of flows) {
164
+ const fn = this.lock.functions[fnId]
165
+ if (!fn) continue
166
+
167
+ // Find outgoing calls
168
+ for (const calleeId of fn.calls || []) {
169
+ const calleeFlow = flows.get(calleeId)
170
+ if (!calleeFlow) continue
171
+
172
+ // Create type edges for parameters
173
+ for (let i = 0; i < calleeFlow.paramTypes.length; i++) {
174
+ const param = calleeFlow.paramTypes[i]
175
+ if (param.type !== 'unknown') {
176
+ flow.outgoingTypes.push({
177
+ from: fnId,
178
+ to: calleeId,
179
+ type: param.type,
180
+ confidence: param.confidence * 0.8,
181
+ })
182
+ }
183
+ }
184
+
185
+ // Create type edges for return type
186
+ if (calleeFlow.returnType !== 'unknown') {
187
+ flow.incomingTypes.push({
188
+ from: calleeId,
189
+ to: fnId,
190
+ type: calleeFlow.returnType,
191
+ confidence: calleeFlow.confidence * 0.8,
192
+ })
193
+ }
194
+ }
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Compute confidence score for type information.
200
+ */
201
+ private computeConfidence(paramTypes: TypeParam[], returnType?: string): number {
202
+ let totalConfidence = 0
203
+ let count = 0
204
+
205
+ for (const param of paramTypes) {
206
+ totalConfidence += param.confidence
207
+ count++
208
+ }
209
+
210
+ if (returnType && returnType !== 'unknown') {
211
+ totalConfidence += 0.9
212
+ count++
213
+ }
214
+
215
+ return count > 0 ? totalConfidence / count : 0.1
216
+ }
217
+
218
+ /**
219
+ * Compute summary statistics.
220
+ */
221
+ private computeSummary(flows: Map<string, TypeFlowInfo>): TypeFlowResult['summary'] {
222
+ const totalFunctions = flows.size
223
+ let typedFunctions = 0
224
+ let inferredFunctions = 0
225
+ let totalConfidence = 0
226
+
227
+ for (const flow of flows.values()) {
228
+ if (flow.returnType !== 'unknown') {
229
+ typedFunctions++
230
+ }
231
+
232
+ const hasInferred = flow.paramTypes.some(p => p.source === 'inference' || p.source === 'usage')
233
+ if (hasInferred) {
234
+ inferredFunctions++
235
+ }
236
+
237
+ totalConfidence += flow.confidence
238
+ }
239
+
240
+ return {
241
+ totalFunctions,
242
+ typedFunctions,
243
+ inferredFunctions,
244
+ averageConfidence: totalFunctions > 0 ? totalConfidence / totalFunctions : 0,
245
+ }
246
+ }
247
+ }