@getmikk/core 1.2.0 → 1.3.1

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 +431 -0
  2. package/package.json +6 -2
  3. package/src/contract/contract-generator.ts +85 -85
  4. package/src/contract/contract-reader.ts +28 -28
  5. package/src/contract/contract-writer.ts +114 -114
  6. package/src/contract/index.ts +12 -12
  7. package/src/contract/lock-compiler.ts +221 -221
  8. package/src/contract/lock-reader.ts +34 -34
  9. package/src/contract/schema.ts +147 -147
  10. package/src/graph/cluster-detector.ts +312 -312
  11. package/src/graph/graph-builder.ts +211 -211
  12. package/src/graph/impact-analyzer.ts +55 -55
  13. package/src/graph/index.ts +4 -4
  14. package/src/graph/types.ts +59 -59
  15. package/src/hash/file-hasher.ts +30 -30
  16. package/src/hash/hash-store.ts +119 -119
  17. package/src/hash/index.ts +3 -3
  18. package/src/hash/tree-hasher.ts +20 -20
  19. package/src/index.ts +12 -12
  20. package/src/parser/base-parser.ts +16 -16
  21. package/src/parser/boundary-checker.ts +211 -211
  22. package/src/parser/index.ts +46 -46
  23. package/src/parser/types.ts +90 -90
  24. package/src/parser/typescript/ts-extractor.ts +543 -543
  25. package/src/parser/typescript/ts-parser.ts +41 -41
  26. package/src/parser/typescript/ts-resolver.ts +86 -86
  27. package/src/utils/errors.ts +42 -42
  28. package/src/utils/fs.ts +75 -75
  29. package/src/utils/fuzzy-match.ts +186 -186
  30. package/src/utils/logger.ts +36 -36
  31. package/src/utils/minimatch.ts +19 -19
  32. package/tests/contract.test.ts +134 -134
  33. package/tests/fixtures/simple-api/package.json +5 -5
  34. package/tests/fixtures/simple-api/src/auth/middleware.ts +9 -9
  35. package/tests/fixtures/simple-api/src/auth/verify.ts +6 -6
  36. package/tests/fixtures/simple-api/src/index.ts +9 -9
  37. package/tests/fixtures/simple-api/src/utils/jwt.ts +3 -3
  38. package/tests/fixtures/simple-api/tsconfig.json +8 -8
  39. package/tests/fuzzy-match.test.ts +142 -142
  40. package/tests/graph.test.ts +169 -169
  41. package/tests/hash.test.ts +49 -49
  42. package/tests/helpers.ts +83 -83
  43. package/tests/parser.test.ts +218 -218
  44. package/tsconfig.json +15 -15
@@ -1,211 +1,211 @@
1
- import * as path from 'node:path'
2
- import type { DependencyGraph, GraphNode, GraphEdge } from './types.js'
3
- import type { ParsedFile, ParsedFunction, ParsedClass } from '../parser/types.js'
4
-
5
- /**
6
- * GraphBuilder — takes parsed files and builds the dependency graph.
7
- * Two-pass approach: first add all nodes, then add all edges.
8
- */
9
- export class GraphBuilder {
10
- /** Main entry point — takes all parsed files and returns the complete graph */
11
- build(files: ParsedFile[]): DependencyGraph {
12
- const graph: DependencyGraph = {
13
- nodes: new Map(),
14
- edges: [],
15
- outEdges: new Map(),
16
- inEdges: new Map(),
17
- }
18
-
19
- // First pass: add all nodes
20
- for (const file of files) {
21
- this.addFileNode(graph, file)
22
- for (const fn of file.functions) {
23
- this.addFunctionNode(graph, fn)
24
- }
25
- for (const cls of file.classes || []) {
26
- this.addClassNode(graph, cls, file.path)
27
- }
28
- for (const gen of file.generics || []) {
29
- this.addGenericNode(graph, gen)
30
- }
31
- }
32
-
33
- // Second pass: add all edges
34
- for (const file of files) {
35
- this.addImportEdges(graph, file)
36
- this.addCallEdges(graph, file)
37
- this.addContainmentEdges(graph, file)
38
- }
39
-
40
- // Third pass: build adjacency maps for fast lookup
41
- this.buildAdjacencyMaps(graph)
42
-
43
- return graph
44
- }
45
-
46
- private addFileNode(graph: DependencyGraph, file: ParsedFile): void {
47
- graph.nodes.set(file.path, {
48
- id: file.path,
49
- type: 'file',
50
- label: path.basename(file.path),
51
- file: file.path,
52
- metadata: { hash: file.hash },
53
- })
54
- }
55
-
56
- private addFunctionNode(graph: DependencyGraph, fn: ParsedFunction): void {
57
- graph.nodes.set(fn.id, {
58
- id: fn.id,
59
- type: 'function',
60
- label: fn.name,
61
- file: fn.file,
62
- metadata: {
63
- startLine: fn.startLine,
64
- endLine: fn.endLine,
65
- isExported: fn.isExported,
66
- isAsync: fn.isAsync,
67
- hash: fn.hash,
68
- purpose: fn.purpose,
69
- edgeCasesHandled: fn.edgeCasesHandled,
70
- errorHandling: fn.errorHandling,
71
- detailedLines: fn.detailedLines,
72
- },
73
- })
74
- }
75
-
76
- private addClassNode(graph: DependencyGraph, cls: ParsedClass, filePath: string): void {
77
- // Add a node for the class itself
78
- graph.nodes.set(cls.id, {
79
- id: cls.id,
80
- type: 'class',
81
- label: cls.name,
82
- file: filePath,
83
- metadata: {
84
- startLine: cls.startLine,
85
- endLine: cls.endLine,
86
- isExported: cls.isExported,
87
- purpose: cls.purpose,
88
- edgeCasesHandled: cls.edgeCasesHandled,
89
- errorHandling: cls.errorHandling,
90
- },
91
- })
92
- // Add nodes for each method
93
- for (const method of cls.methods) {
94
- this.addFunctionNode(graph, method)
95
- }
96
- }
97
-
98
- private addGenericNode(graph: DependencyGraph, gen: any): void {
99
- graph.nodes.set(gen.id, {
100
- id: gen.id,
101
- type: 'generic',
102
- label: gen.name,
103
- file: gen.file,
104
- metadata: {
105
- startLine: gen.startLine,
106
- endLine: gen.endLine,
107
- isExported: gen.isExported,
108
- purpose: gen.purpose,
109
- hash: gen.type, // reusing hash or just storing the type string
110
- },
111
- })
112
- }
113
-
114
- /** Creates edges for import statements: fileA imports fileB → edge(A, B, 'imports') */
115
- private addImportEdges(graph: DependencyGraph, file: ParsedFile): void {
116
- for (const imp of file.imports) {
117
- if (imp.resolvedPath && graph.nodes.has(imp.resolvedPath)) {
118
- graph.edges.push({
119
- source: file.path,
120
- target: imp.resolvedPath,
121
- type: 'imports',
122
- })
123
- }
124
- }
125
- }
126
-
127
- /** Creates edges for function calls: fnA calls fnB → edge(A, B, 'calls') */
128
- private addCallEdges(graph: DependencyGraph, file: ParsedFile): void {
129
- // Build a map of import names to function IDs for resolving calls
130
- const importedNames = new Map<string, string>()
131
- for (const imp of file.imports) {
132
- if (imp.resolvedPath) {
133
- for (const name of imp.names) {
134
- importedNames.set(name, `fn:${imp.resolvedPath}:${name}`)
135
- }
136
- }
137
- }
138
-
139
- const allFunctions = [...file.functions, ...file.classes.flatMap(c => c.methods)]
140
-
141
- for (const fn of allFunctions) {
142
- for (const call of fn.calls) {
143
- // Try to resolve: first check imported names, then local functions
144
- const simpleName = call.includes('.') ? call.split('.').pop()! : call
145
-
146
- // Check if it's an imported function
147
- const importedId = importedNames.get(simpleName) || importedNames.get(call)
148
- if (importedId && graph.nodes.has(importedId)) {
149
- graph.edges.push({
150
- source: fn.id,
151
- target: importedId,
152
- type: 'calls',
153
- })
154
- continue
155
- }
156
-
157
- // Check if it's a local function in the same file
158
- const localId = `fn:${file.path}:${simpleName}`
159
- if (graph.nodes.has(localId) && localId !== fn.id) {
160
- graph.edges.push({
161
- source: fn.id,
162
- target: localId,
163
- type: 'calls',
164
- })
165
- }
166
- }
167
- }
168
- }
169
-
170
- /** Creates containment edges: file contains function → edge(file, fn, 'contains') */
171
- private addContainmentEdges(graph: DependencyGraph, file: ParsedFile): void {
172
- for (const fn of file.functions) {
173
- graph.edges.push({
174
- source: file.path,
175
- target: fn.id,
176
- type: 'contains',
177
- })
178
- }
179
- for (const cls of file.classes) {
180
- graph.edges.push({
181
- source: file.path,
182
- target: cls.id,
183
- type: 'contains',
184
- })
185
- for (const method of cls.methods) {
186
- graph.edges.push({
187
- source: cls.id,
188
- target: method.id,
189
- type: 'contains',
190
- })
191
- }
192
- }
193
- }
194
-
195
- /** Build adjacency maps from edge list for O(1) lookups */
196
- private buildAdjacencyMaps(graph: DependencyGraph): void {
197
- for (const edge of graph.edges) {
198
- // outEdges
199
- if (!graph.outEdges.has(edge.source)) {
200
- graph.outEdges.set(edge.source, [])
201
- }
202
- graph.outEdges.get(edge.source)!.push(edge)
203
-
204
- // inEdges
205
- if (!graph.inEdges.has(edge.target)) {
206
- graph.inEdges.set(edge.target, [])
207
- }
208
- graph.inEdges.get(edge.target)!.push(edge)
209
- }
210
- }
211
- }
1
+ import * as path from 'node:path'
2
+ import type { DependencyGraph, GraphNode, GraphEdge } from './types.js'
3
+ import type { ParsedFile, ParsedFunction, ParsedClass } from '../parser/types.js'
4
+
5
+ /**
6
+ * GraphBuilder — takes parsed files and builds the dependency graph.
7
+ * Two-pass approach: first add all nodes, then add all edges.
8
+ */
9
+ export class GraphBuilder {
10
+ /** Main entry point — takes all parsed files and returns the complete graph */
11
+ build(files: ParsedFile[]): DependencyGraph {
12
+ const graph: DependencyGraph = {
13
+ nodes: new Map(),
14
+ edges: [],
15
+ outEdges: new Map(),
16
+ inEdges: new Map(),
17
+ }
18
+
19
+ // First pass: add all nodes
20
+ for (const file of files) {
21
+ this.addFileNode(graph, file)
22
+ for (const fn of file.functions) {
23
+ this.addFunctionNode(graph, fn)
24
+ }
25
+ for (const cls of file.classes || []) {
26
+ this.addClassNode(graph, cls, file.path)
27
+ }
28
+ for (const gen of file.generics || []) {
29
+ this.addGenericNode(graph, gen)
30
+ }
31
+ }
32
+
33
+ // Second pass: add all edges
34
+ for (const file of files) {
35
+ this.addImportEdges(graph, file)
36
+ this.addCallEdges(graph, file)
37
+ this.addContainmentEdges(graph, file)
38
+ }
39
+
40
+ // Third pass: build adjacency maps for fast lookup
41
+ this.buildAdjacencyMaps(graph)
42
+
43
+ return graph
44
+ }
45
+
46
+ private addFileNode(graph: DependencyGraph, file: ParsedFile): void {
47
+ graph.nodes.set(file.path, {
48
+ id: file.path,
49
+ type: 'file',
50
+ label: path.basename(file.path),
51
+ file: file.path,
52
+ metadata: { hash: file.hash },
53
+ })
54
+ }
55
+
56
+ private addFunctionNode(graph: DependencyGraph, fn: ParsedFunction): void {
57
+ graph.nodes.set(fn.id, {
58
+ id: fn.id,
59
+ type: 'function',
60
+ label: fn.name,
61
+ file: fn.file,
62
+ metadata: {
63
+ startLine: fn.startLine,
64
+ endLine: fn.endLine,
65
+ isExported: fn.isExported,
66
+ isAsync: fn.isAsync,
67
+ hash: fn.hash,
68
+ purpose: fn.purpose,
69
+ edgeCasesHandled: fn.edgeCasesHandled,
70
+ errorHandling: fn.errorHandling,
71
+ detailedLines: fn.detailedLines,
72
+ },
73
+ })
74
+ }
75
+
76
+ private addClassNode(graph: DependencyGraph, cls: ParsedClass, filePath: string): void {
77
+ // Add a node for the class itself
78
+ graph.nodes.set(cls.id, {
79
+ id: cls.id,
80
+ type: 'class',
81
+ label: cls.name,
82
+ file: filePath,
83
+ metadata: {
84
+ startLine: cls.startLine,
85
+ endLine: cls.endLine,
86
+ isExported: cls.isExported,
87
+ purpose: cls.purpose,
88
+ edgeCasesHandled: cls.edgeCasesHandled,
89
+ errorHandling: cls.errorHandling,
90
+ },
91
+ })
92
+ // Add nodes for each method
93
+ for (const method of cls.methods) {
94
+ this.addFunctionNode(graph, method)
95
+ }
96
+ }
97
+
98
+ private addGenericNode(graph: DependencyGraph, gen: any): void {
99
+ graph.nodes.set(gen.id, {
100
+ id: gen.id,
101
+ type: 'generic',
102
+ label: gen.name,
103
+ file: gen.file,
104
+ metadata: {
105
+ startLine: gen.startLine,
106
+ endLine: gen.endLine,
107
+ isExported: gen.isExported,
108
+ purpose: gen.purpose,
109
+ hash: gen.type, // reusing hash or just storing the type string
110
+ },
111
+ })
112
+ }
113
+
114
+ /** Creates edges for import statements: fileA imports fileB → edge(A, B, 'imports') */
115
+ private addImportEdges(graph: DependencyGraph, file: ParsedFile): void {
116
+ for (const imp of file.imports) {
117
+ if (imp.resolvedPath && graph.nodes.has(imp.resolvedPath)) {
118
+ graph.edges.push({
119
+ source: file.path,
120
+ target: imp.resolvedPath,
121
+ type: 'imports',
122
+ })
123
+ }
124
+ }
125
+ }
126
+
127
+ /** Creates edges for function calls: fnA calls fnB → edge(A, B, 'calls') */
128
+ private addCallEdges(graph: DependencyGraph, file: ParsedFile): void {
129
+ // Build a map of import names to function IDs for resolving calls
130
+ const importedNames = new Map<string, string>()
131
+ for (const imp of file.imports) {
132
+ if (imp.resolvedPath) {
133
+ for (const name of imp.names) {
134
+ importedNames.set(name, `fn:${imp.resolvedPath}:${name}`)
135
+ }
136
+ }
137
+ }
138
+
139
+ const allFunctions = [...file.functions, ...file.classes.flatMap(c => c.methods)]
140
+
141
+ for (const fn of allFunctions) {
142
+ for (const call of fn.calls) {
143
+ // Try to resolve: first check imported names, then local functions
144
+ const simpleName = call.includes('.') ? call.split('.').pop()! : call
145
+
146
+ // Check if it's an imported function
147
+ const importedId = importedNames.get(simpleName) || importedNames.get(call)
148
+ if (importedId && graph.nodes.has(importedId)) {
149
+ graph.edges.push({
150
+ source: fn.id,
151
+ target: importedId,
152
+ type: 'calls',
153
+ })
154
+ continue
155
+ }
156
+
157
+ // Check if it's a local function in the same file
158
+ const localId = `fn:${file.path}:${simpleName}`
159
+ if (graph.nodes.has(localId) && localId !== fn.id) {
160
+ graph.edges.push({
161
+ source: fn.id,
162
+ target: localId,
163
+ type: 'calls',
164
+ })
165
+ }
166
+ }
167
+ }
168
+ }
169
+
170
+ /** Creates containment edges: file contains function → edge(file, fn, 'contains') */
171
+ private addContainmentEdges(graph: DependencyGraph, file: ParsedFile): void {
172
+ for (const fn of file.functions) {
173
+ graph.edges.push({
174
+ source: file.path,
175
+ target: fn.id,
176
+ type: 'contains',
177
+ })
178
+ }
179
+ for (const cls of file.classes) {
180
+ graph.edges.push({
181
+ source: file.path,
182
+ target: cls.id,
183
+ type: 'contains',
184
+ })
185
+ for (const method of cls.methods) {
186
+ graph.edges.push({
187
+ source: cls.id,
188
+ target: method.id,
189
+ type: 'contains',
190
+ })
191
+ }
192
+ }
193
+ }
194
+
195
+ /** Build adjacency maps from edge list for O(1) lookups */
196
+ private buildAdjacencyMaps(graph: DependencyGraph): void {
197
+ for (const edge of graph.edges) {
198
+ // outEdges
199
+ if (!graph.outEdges.has(edge.source)) {
200
+ graph.outEdges.set(edge.source, [])
201
+ }
202
+ graph.outEdges.get(edge.source)!.push(edge)
203
+
204
+ // inEdges
205
+ if (!graph.inEdges.has(edge.target)) {
206
+ graph.inEdges.set(edge.target, [])
207
+ }
208
+ graph.inEdges.get(edge.target)!.push(edge)
209
+ }
210
+ }
211
+ }
@@ -1,55 +1,55 @@
1
- import type { DependencyGraph, ImpactResult } from './types.js'
2
-
3
- /**
4
- * ImpactAnalyzer — Given changed nodes, walks the graph backwards (BFS)
5
- * to find everything that depends on them.
6
- * Powers "what breaks if I change X?"
7
- */
8
- export class ImpactAnalyzer {
9
- constructor(private graph: DependencyGraph) { }
10
-
11
- /** Given a list of changed node IDs, find everything impacted */
12
- analyze(changedNodeIds: string[]): ImpactResult {
13
- const visited = new Set<string>()
14
- const queue: { id: string; depth: number }[] = changedNodeIds.map(id => ({ id, depth: 0 }))
15
- let maxDepth = 0
16
-
17
- while (queue.length > 0) {
18
- const { id: current, depth } = queue.shift()!
19
- if (visited.has(current)) continue
20
- visited.add(current)
21
- maxDepth = Math.max(maxDepth, depth)
22
-
23
- // Find everything that depends on current (incoming edges)
24
- const dependents = this.graph.inEdges.get(current) || []
25
- for (const edge of dependents) {
26
- if (!visited.has(edge.source) && edge.type !== 'contains') {
27
- queue.push({ id: edge.source, depth: depth + 1 })
28
- }
29
- }
30
- }
31
-
32
- const impacted = [...visited].filter(id => !changedNodeIds.includes(id))
33
-
34
- return {
35
- changed: changedNodeIds,
36
- impacted,
37
- depth: maxDepth,
38
- confidence: this.computeConfidence(impacted.length, maxDepth),
39
- }
40
- }
41
-
42
- /**
43
- * How confident are we in this impact analysis?
44
- * High = few nodes affected, shallow depth
45
- * Low = many nodes affected, deep chains
46
- */
47
- private computeConfidence(
48
- impactedCount: number,
49
- depth: number
50
- ): 'high' | 'medium' | 'low' {
51
- if (impactedCount < 5 && depth < 3) return 'high'
52
- if (impactedCount < 20 && depth < 6) return 'medium'
53
- return 'low'
54
- }
55
- }
1
+ import type { DependencyGraph, ImpactResult } from './types.js'
2
+
3
+ /**
4
+ * ImpactAnalyzer — Given changed nodes, walks the graph backwards (BFS)
5
+ * to find everything that depends on them.
6
+ * Powers "what breaks if I change X?"
7
+ */
8
+ export class ImpactAnalyzer {
9
+ constructor(private graph: DependencyGraph) { }
10
+
11
+ /** Given a list of changed node IDs, find everything impacted */
12
+ analyze(changedNodeIds: string[]): ImpactResult {
13
+ const visited = new Set<string>()
14
+ const queue: { id: string; depth: number }[] = changedNodeIds.map(id => ({ id, depth: 0 }))
15
+ let maxDepth = 0
16
+
17
+ while (queue.length > 0) {
18
+ const { id: current, depth } = queue.shift()!
19
+ if (visited.has(current)) continue
20
+ visited.add(current)
21
+ maxDepth = Math.max(maxDepth, depth)
22
+
23
+ // Find everything that depends on current (incoming edges)
24
+ const dependents = this.graph.inEdges.get(current) || []
25
+ for (const edge of dependents) {
26
+ if (!visited.has(edge.source) && edge.type !== 'contains') {
27
+ queue.push({ id: edge.source, depth: depth + 1 })
28
+ }
29
+ }
30
+ }
31
+
32
+ const impacted = [...visited].filter(id => !changedNodeIds.includes(id))
33
+
34
+ return {
35
+ changed: changedNodeIds,
36
+ impacted,
37
+ depth: maxDepth,
38
+ confidence: this.computeConfidence(impacted.length, maxDepth),
39
+ }
40
+ }
41
+
42
+ /**
43
+ * How confident are we in this impact analysis?
44
+ * High = few nodes affected, shallow depth
45
+ * Low = many nodes affected, deep chains
46
+ */
47
+ private computeConfidence(
48
+ impactedCount: number,
49
+ depth: number
50
+ ): 'high' | 'medium' | 'low' {
51
+ if (impactedCount < 5 && depth < 3) return 'high'
52
+ if (impactedCount < 20 && depth < 6) return 'medium'
53
+ return 'low'
54
+ }
55
+ }
@@ -1,4 +1,4 @@
1
- export type { DependencyGraph, GraphNode, GraphEdge, ImpactResult, NodeType, EdgeType, ModuleCluster } from './types.js'
2
- export { GraphBuilder } from './graph-builder.js'
3
- export { ImpactAnalyzer } from './impact-analyzer.js'
4
- export { ClusterDetector } from './cluster-detector.js'
1
+ export type { DependencyGraph, GraphNode, GraphEdge, ImpactResult, NodeType, EdgeType, ModuleCluster } from './types.js'
2
+ export { GraphBuilder } from './graph-builder.js'
3
+ export { ImpactAnalyzer } from './impact-analyzer.js'
4
+ export { ClusterDetector } from './cluster-detector.js'
@@ -1,59 +1,59 @@
1
- /**
2
- * Graph types — nodes, edges, and the dependency graph itself.
3
- */
4
-
5
- export type NodeType = 'function' | 'file' | 'module' | 'class' | 'generic'
6
- export type EdgeType = 'calls' | 'imports' | 'exports' | 'contains'
7
-
8
- /** A single node in the dependency graph */
9
- export interface GraphNode {
10
- id: string // "fn:src/auth/verify.ts:verifyToken"
11
- type: NodeType
12
- label: string // "verifyToken"
13
- file: string // "src/auth/verify.ts"
14
- moduleId?: string // "auth" — which declared module this belongs to
15
- metadata: {
16
- startLine?: number
17
- endLine?: number
18
- isExported?: boolean
19
- isAsync?: boolean
20
- hash?: string
21
- purpose?: string
22
- edgeCasesHandled?: string[]
23
- errorHandling?: { line: number; type: 'try-catch' | 'throw'; detail: string }[]
24
- detailedLines?: { startLine: number; endLine: number; blockType: string }[]
25
- }
26
- }
27
-
28
- /** A single edge in the dependency graph */
29
- export interface GraphEdge {
30
- source: string // "fn:src/auth/verify.ts:verifyToken"
31
- target: string // "fn:src/utils/jwt.ts:jwtDecode"
32
- type: EdgeType
33
- weight?: number // How often this call happens (for coupling metrics)
34
- }
35
-
36
- /** The full dependency graph */
37
- export interface DependencyGraph {
38
- nodes: Map<string, GraphNode>
39
- edges: GraphEdge[]
40
- outEdges: Map<string, GraphEdge[]> // node → [edges going out]
41
- inEdges: Map<string, GraphEdge[]> // node → [edges coming in]
42
- }
43
-
44
- /** Result of impact analysis */
45
- export interface ImpactResult {
46
- changed: string[] // The directly changed nodes
47
- impacted: string[] // Everything that depends on changed nodes
48
- depth: number // How many hops from change to furthest impact
49
- confidence: 'high' | 'medium' | 'low'
50
- }
51
-
52
- /** A cluster of files that naturally belong together */
53
- export interface ModuleCluster {
54
- id: string
55
- files: string[]
56
- confidence: number // 0.0 to 1.0
57
- suggestedName: string // inferred from folder names
58
- functions: string[] // function IDs in this cluster
59
- }
1
+ /**
2
+ * Graph types — nodes, edges, and the dependency graph itself.
3
+ */
4
+
5
+ export type NodeType = 'function' | 'file' | 'module' | 'class' | 'generic'
6
+ export type EdgeType = 'calls' | 'imports' | 'exports' | 'contains'
7
+
8
+ /** A single node in the dependency graph */
9
+ export interface GraphNode {
10
+ id: string // "fn:src/auth/verify.ts:verifyToken"
11
+ type: NodeType
12
+ label: string // "verifyToken"
13
+ file: string // "src/auth/verify.ts"
14
+ moduleId?: string // "auth" — which declared module this belongs to
15
+ metadata: {
16
+ startLine?: number
17
+ endLine?: number
18
+ isExported?: boolean
19
+ isAsync?: boolean
20
+ hash?: string
21
+ purpose?: string
22
+ edgeCasesHandled?: string[]
23
+ errorHandling?: { line: number; type: 'try-catch' | 'throw'; detail: string }[]
24
+ detailedLines?: { startLine: number; endLine: number; blockType: string }[]
25
+ }
26
+ }
27
+
28
+ /** A single edge in the dependency graph */
29
+ export interface GraphEdge {
30
+ source: string // "fn:src/auth/verify.ts:verifyToken"
31
+ target: string // "fn:src/utils/jwt.ts:jwtDecode"
32
+ type: EdgeType
33
+ weight?: number // How often this call happens (for coupling metrics)
34
+ }
35
+
36
+ /** The full dependency graph */
37
+ export interface DependencyGraph {
38
+ nodes: Map<string, GraphNode>
39
+ edges: GraphEdge[]
40
+ outEdges: Map<string, GraphEdge[]> // node → [edges going out]
41
+ inEdges: Map<string, GraphEdge[]> // node → [edges coming in]
42
+ }
43
+
44
+ /** Result of impact analysis */
45
+ export interface ImpactResult {
46
+ changed: string[] // The directly changed nodes
47
+ impacted: string[] // Everything that depends on changed nodes
48
+ depth: number // How many hops from change to furthest impact
49
+ confidence: 'high' | 'medium' | 'low'
50
+ }
51
+
52
+ /** A cluster of files that naturally belong together */
53
+ export interface ModuleCluster {
54
+ id: string
55
+ files: string[]
56
+ confidence: number // 0.0 to 1.0
57
+ suggestedName: string // inferred from folder names
58
+ functions: string[] // function IDs in this cluster
59
+ }