@getmikk/core 2.0.12 → 2.0.14

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 (44) hide show
  1. package/README.md +12 -3
  2. package/package.json +1 -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 +272 -0
  7. package/src/cache/index.ts +1 -0
  8. package/src/contract/adr-manager.ts +5 -4
  9. package/src/contract/contract-generator.ts +31 -3
  10. package/src/contract/contract-writer.ts +3 -2
  11. package/src/contract/lock-compiler.ts +34 -0
  12. package/src/contract/lock-reader.ts +62 -5
  13. package/src/contract/schema.ts +10 -0
  14. package/src/index.ts +14 -1
  15. package/src/parser/error-recovery.ts +646 -0
  16. package/src/parser/index.ts +330 -74
  17. package/src/parser/oxc-parser.ts +3 -2
  18. package/src/parser/tree-sitter/parser.ts +59 -9
  19. package/src/parser/tree-sitter/queries.ts +27 -0
  20. package/src/parser/types.ts +1 -1
  21. package/src/security/index.ts +1 -0
  22. package/src/security/scanner.ts +342 -0
  23. package/src/utils/artifact-transaction.ts +176 -0
  24. package/src/utils/atomic-write.ts +131 -0
  25. package/src/utils/fs.ts +76 -25
  26. package/src/utils/language-registry.ts +95 -0
  27. package/src/utils/minimatch.ts +49 -6
  28. package/tests/adr-manager.test.ts +6 -0
  29. package/tests/artifact-transaction.test.ts +73 -0
  30. package/tests/contract.test.ts +12 -0
  31. package/tests/dead-code.test.ts +12 -0
  32. package/tests/esm-resolver.test.ts +6 -0
  33. package/tests/fs.test.ts +22 -1
  34. package/tests/fuzzy-match.test.ts +6 -0
  35. package/tests/go-parser.test.ts +7 -0
  36. package/tests/graph.test.ts +10 -0
  37. package/tests/hash.test.ts +6 -0
  38. package/tests/impact-classified.test.ts +13 -0
  39. package/tests/js-parser.test.ts +10 -0
  40. package/tests/language-registry.test.ts +64 -0
  41. package/tests/parse-diagnostics.test.ts +115 -0
  42. package/tests/parser.test.ts +36 -0
  43. package/tests/tree-sitter-parser.test.ts +201 -0
  44. package/tests/ts-parser.test.ts +6 -0
package/README.md CHANGED
@@ -15,13 +15,16 @@ Foundation package for the Mikk ecosystem. All other packages depend on core —
15
15
 
16
16
  ### Parsers
17
17
 
18
- Three language parsers, each following the same interface: `parse(filePath, content)` → `ParsedFile`.
18
+ Three parser families follow the same interface: `parse(filePath, content)` → `ParsedFile`.
19
19
 
20
20
  **TypeScript / TSX**
21
- Uses the TypeScript Compiler API. Extracts: functions (name, params with types, return type, start/end line, async flag, decorators, generics), classes (methods, properties, inheritance), imports (named, default, namespace, type-only) with full resolution (tsconfig `paths` alias resolution, recursive `extends` chain, index file inference, extension inference). Every extracted function has its exact byte-accurate body location.
21
+ Uses OXC (Rust parser). Extracts: functions (name, params with types, return type, start/end line, async flag, decorators, generics), classes (methods, properties, inheritance), imports (named, default, namespace, type-only) with full resolution (tsconfig `paths` alias resolution, recursive `extends` chain, index file inference, extension inference). Every extracted function has its exact byte-accurate body location.
22
22
 
23
23
  **JavaScript / JSX**
24
- Uses the TypeScript Compiler API with `ScriptKind` inference (detects JS/JSX/CJS/MJS). Handles: JSX expression containers, default exports, CommonJS `module.exports`, re-exports via barrel files.
24
+ Uses OXC with `ScriptKind` inference (detects JS/JSX/CJS/MJS). Handles: JSX expression containers, default exports, CommonJS `module.exports`, re-exports via barrel files.
25
+
26
+ **Polyglot (Tree-sitter)**
27
+ Python, Java, Kotlin (`.kt`, `.kts`), Swift, C/C++ (`.cpp`, `.cc`, `.cxx`, `.hpp`, `.hxx`, `.hh`), C#, Rust, PHP, and Ruby via tree-sitter grammars.
25
28
 
26
29
  **Go**
27
30
  Regex + stateful scanning. No Go toolchain dependency. Extracts: functions, methods (with receiver types), structs, interfaces, package imports. `go.mod` used for project boundary detection.
@@ -81,6 +84,12 @@ Lock format v1.7.0:
81
84
 
82
85
  Read and write `mikk.json` and `mikk.lock.json`. `LockReader.write()` uses atomic temp-file + rename to prevent corruption.
83
86
 
87
+ `AdrManager` writes `mikk.json` atomically as well (temp file + rename + file lock), reducing corruption risk in concurrent agent workflows.
88
+
89
+ ### Parse Diagnostics
90
+
91
+ `parseFilesWithDiagnostics` returns both parsed files and parser/read/import-resolution diagnostics. This enables strict parse enforcement in CLI commands (`mikk init --strict-parsing`, `mikk analyze --strict-parsing`) for high-assurance pipelines.
92
+
84
93
  ### AdrManager
85
94
 
86
95
  CRUD for Architectural Decision Records in `mikk.json`. Add, update, remove, list, and get individual decisions. ADRs surface in all AI context queries via the MCP server.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getmikk/core",
3
- "version": "2.0.12",
3
+ "version": "2.0.14",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
@@ -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, GraphEdge } 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
+ }