@getmikk/core 1.5.1 → 1.7.0

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.
@@ -1,9 +1,15 @@
1
- import type { DependencyGraph, ImpactResult } from './types.js'
1
+ import type { DependencyGraph, ImpactResult, ClassifiedImpact, RiskLevel } from './types.js'
2
2
 
3
3
  /**
4
4
  * ImpactAnalyzer — Given changed nodes, walks the graph backwards (BFS)
5
5
  * to find everything that depends on them.
6
6
  * Powers "what breaks if I change X?"
7
+ *
8
+ * Now includes risk classification:
9
+ * CRITICAL = direct caller (depth 1) that crosses a module boundary
10
+ * HIGH = direct caller (depth 1) within the same module
11
+ * MEDIUM = depth 2
12
+ * LOW = depth 3+
7
13
  */
8
14
  export class ImpactAnalyzer {
9
15
  constructor(private graph: DependencyGraph) { }
@@ -11,13 +17,24 @@ export class ImpactAnalyzer {
11
17
  /** Given a list of changed node IDs, find everything impacted */
12
18
  analyze(changedNodeIds: string[]): ImpactResult {
13
19
  const visited = new Set<string>()
20
+ const depthMap = new Map<string, number>()
14
21
  const queue: { id: string; depth: number }[] = changedNodeIds.map(id => ({ id, depth: 0 }))
15
22
  let maxDepth = 0
16
23
 
24
+ const changedSet = new Set(changedNodeIds)
25
+
26
+ // Collect module IDs of the changed nodes
27
+ const changedModules = new Set<string | undefined>()
28
+ for (const id of changedNodeIds) {
29
+ const node = this.graph.nodes.get(id)
30
+ if (node) changedModules.add(node.moduleId)
31
+ }
32
+
17
33
  while (queue.length > 0) {
18
34
  const { id: current, depth } = queue.shift()!
19
35
  if (visited.has(current)) continue
20
36
  visited.add(current)
37
+ depthMap.set(current, depth)
21
38
  maxDepth = Math.max(maxDepth, depth)
22
39
 
23
40
  // Find everything that depends on current (incoming edges)
@@ -29,13 +46,47 @@ export class ImpactAnalyzer {
29
46
  }
30
47
  }
31
48
 
32
- const impacted = [...visited].filter(id => !changedNodeIds.includes(id))
49
+ const impacted = [...visited].filter(id => !changedSet.has(id))
50
+
51
+ // Classify each impacted node by risk level
52
+ const classified: ImpactResult['classified'] = {
53
+ critical: [],
54
+ high: [],
55
+ medium: [],
56
+ low: [],
57
+ }
58
+
59
+ for (const id of impacted) {
60
+ const node = this.graph.nodes.get(id)
61
+ if (!node) continue
62
+
63
+ const depth = depthMap.get(id) ?? 999
64
+ const crossesBoundary = !changedModules.has(node.moduleId)
65
+
66
+ const risk: RiskLevel =
67
+ depth === 1 && crossesBoundary ? 'critical' :
68
+ depth === 1 ? 'high' :
69
+ depth === 2 ? 'medium' :
70
+ 'low'
71
+
72
+ const entry: ClassifiedImpact = {
73
+ nodeId: id,
74
+ label: node.label,
75
+ file: node.file,
76
+ moduleId: node.moduleId,
77
+ risk,
78
+ depth,
79
+ }
80
+
81
+ classified[risk].push(entry)
82
+ }
33
83
 
34
84
  return {
35
85
  changed: changedNodeIds,
36
86
  impacted,
37
87
  depth: maxDepth,
38
88
  confidence: this.computeConfidence(impacted.length, maxDepth),
89
+ classified,
39
90
  }
40
91
  }
41
92
 
@@ -1,4 +1,7 @@
1
- export type { DependencyGraph, GraphNode, GraphEdge, ImpactResult, NodeType, EdgeType, ModuleCluster } from './types.js'
1
+ export type { DependencyGraph, GraphNode, GraphEdge, ImpactResult, NodeType, EdgeType, ModuleCluster, RiskLevel, ClassifiedImpact } from './types.js'
2
2
  export { GraphBuilder } from './graph-builder.js'
3
3
  export { ImpactAnalyzer } from './impact-analyzer.js'
4
4
  export { ClusterDetector } from './cluster-detector.js'
5
+ export { DeadCodeDetector } from './dead-code-detector.js'
6
+ export type { DeadCodeResult, DeadCodeEntry } from './dead-code-detector.js'
7
+
@@ -33,6 +33,7 @@ export interface GraphEdge {
33
33
  target: string // "fn:src/utils/jwt.ts:jwtDecode"
34
34
  type: EdgeType
35
35
  weight?: number // How often this call happens (for coupling metrics)
36
+ confidence?: number // 0.0–1.0: 1.0 = direct AST call, 0.8 = via interface, 0.5 = fuzzy/inferred
36
37
  }
37
38
 
38
39
  /** The full dependency graph */
@@ -43,12 +44,32 @@ export interface DependencyGraph {
43
44
  inEdges: Map<string, GraphEdge[]> // node → [edges coming in]
44
45
  }
45
46
 
47
+ /** Risk level for an impacted node */
48
+ export type RiskLevel = 'critical' | 'high' | 'medium' | 'low'
49
+
50
+ /** A single node in the classified impact result */
51
+ export interface ClassifiedImpact {
52
+ nodeId: string
53
+ label: string
54
+ file: string
55
+ moduleId?: string
56
+ risk: RiskLevel
57
+ depth: number // hops from change
58
+ }
59
+
46
60
  /** Result of impact analysis */
47
61
  export interface ImpactResult {
48
62
  changed: string[] // The directly changed nodes
49
63
  impacted: string[] // Everything that depends on changed nodes
50
64
  depth: number // How many hops from change to furthest impact
51
65
  confidence: 'high' | 'medium' | 'low'
66
+ /** Risk-classified breakdown of impacted nodes */
67
+ classified: {
68
+ critical: ClassifiedImpact[]
69
+ high: ClassifiedImpact[]
70
+ medium: ClassifiedImpact[]
71
+ low: ClassifiedImpact[]
72
+ }
52
73
  }
53
74
 
54
75
  /** A cluster of files that naturally belong together */
package/src/index.ts CHANGED
@@ -10,4 +10,4 @@ export * from './utils/logger.js'
10
10
  export { discoverFiles, discoverContextFiles, readFileContent, writeFileContent, fileExists, setupMikkDirectory, readMikkIgnore, parseMikkIgnore, detectProjectLanguage, getDiscoveryPatterns, generateMikkIgnore } from './utils/fs.js'
11
11
  export type { ContextFile, ContextFileType, ProjectLanguage } from './utils/fs.js'
12
12
  export { minimatch } from './utils/minimatch.js'
13
- export { scoreFunctions, findFuzzyMatches, levenshtein, splitCamelCase, extractKeywords } from './utils/fuzzy-match.js'
13
+ export { scoreFunctions, findFuzzyMatches, levenshtein, splitCamelCase, extractKeywords } from './utils/fuzzy-match.js'