@getmikk/core 1.2.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.
Files changed (43) hide show
  1. package/package.json +29 -0
  2. package/src/contract/contract-generator.ts +85 -0
  3. package/src/contract/contract-reader.ts +28 -0
  4. package/src/contract/contract-writer.ts +114 -0
  5. package/src/contract/index.ts +12 -0
  6. package/src/contract/lock-compiler.ts +221 -0
  7. package/src/contract/lock-reader.ts +34 -0
  8. package/src/contract/schema.ts +147 -0
  9. package/src/graph/cluster-detector.ts +312 -0
  10. package/src/graph/graph-builder.ts +211 -0
  11. package/src/graph/impact-analyzer.ts +55 -0
  12. package/src/graph/index.ts +4 -0
  13. package/src/graph/types.ts +59 -0
  14. package/src/hash/file-hasher.ts +30 -0
  15. package/src/hash/hash-store.ts +119 -0
  16. package/src/hash/index.ts +3 -0
  17. package/src/hash/tree-hasher.ts +20 -0
  18. package/src/index.ts +12 -0
  19. package/src/parser/base-parser.ts +16 -0
  20. package/src/parser/boundary-checker.ts +212 -0
  21. package/src/parser/index.ts +46 -0
  22. package/src/parser/types.ts +90 -0
  23. package/src/parser/typescript/ts-extractor.ts +543 -0
  24. package/src/parser/typescript/ts-parser.ts +41 -0
  25. package/src/parser/typescript/ts-resolver.ts +86 -0
  26. package/src/utils/errors.ts +42 -0
  27. package/src/utils/fs.ts +75 -0
  28. package/src/utils/fuzzy-match.ts +186 -0
  29. package/src/utils/logger.ts +36 -0
  30. package/src/utils/minimatch.ts +19 -0
  31. package/tests/contract.test.ts +134 -0
  32. package/tests/fixtures/simple-api/package.json +5 -0
  33. package/tests/fixtures/simple-api/src/auth/middleware.ts +9 -0
  34. package/tests/fixtures/simple-api/src/auth/verify.ts +6 -0
  35. package/tests/fixtures/simple-api/src/index.ts +9 -0
  36. package/tests/fixtures/simple-api/src/utils/jwt.ts +3 -0
  37. package/tests/fixtures/simple-api/tsconfig.json +8 -0
  38. package/tests/fuzzy-match.test.ts +142 -0
  39. package/tests/graph.test.ts +169 -0
  40. package/tests/hash.test.ts +49 -0
  41. package/tests/helpers.ts +83 -0
  42. package/tests/parser.test.ts +218 -0
  43. package/tsconfig.json +15 -0
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@getmikk/core",
3
+ "version": "1.2.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "test": "bun test",
16
+ "publish": "npm publish --access public",
17
+ "dev": "tsc --watch"
18
+ },
19
+ "dependencies": {
20
+ "@types/better-sqlite3": "^7.6.13",
21
+ "better-sqlite3": "^12.6.2",
22
+ "fast-glob": "^3.3.0",
23
+ "zod": "^3.22.0"
24
+ },
25
+ "devDependencies": {
26
+ "typescript": "^5.7.0",
27
+ "@types/node": "^22.0.0"
28
+ }
29
+ }
@@ -0,0 +1,85 @@
1
+ import type { MikkContract } from './schema.js'
2
+ import type { ModuleCluster } from '../graph/types.js'
3
+ import type { ParsedFile } from '../parser/types.js'
4
+
5
+ /**
6
+ * ContractGenerator — generates a mikk.json skeleton from graph analysis.
7
+ * Takes detected module clusters and produces a human-refinable contract.
8
+ */
9
+ export class ContractGenerator {
10
+ /** Generate a full mikk.json contract from detected clusters */
11
+ generateFromClusters(
12
+ clusters: ModuleCluster[],
13
+ parsedFiles: ParsedFile[],
14
+ projectName: string
15
+ ): MikkContract {
16
+ const modules = clusters.map(cluster => ({
17
+ id: cluster.id,
18
+ name: cluster.suggestedName,
19
+ description: `Contains ${cluster.files.length} files with ${cluster.functions.length} functions`,
20
+ intent: '',
21
+ paths: this.inferPaths(cluster.files),
22
+ entryFunctions: this.inferEntryFunctions(cluster, parsedFiles),
23
+ }))
24
+
25
+ // Detect entry points (files with no importedBy)
26
+ const entryPoints = parsedFiles
27
+ .filter(f => {
28
+ const basename = f.path.split('/').pop() || ''
29
+ return basename === 'index.ts' || basename === 'server.ts' || basename === 'main.ts' || basename === 'app.ts'
30
+ })
31
+ .map(f => f.path)
32
+
33
+ return {
34
+ version: '1.0.0',
35
+ project: {
36
+ name: projectName,
37
+ description: '',
38
+ language: 'typescript',
39
+ entryPoints: entryPoints.length > 0 ? entryPoints : [parsedFiles[0]?.path ?? 'src/index.ts'],
40
+ },
41
+ declared: {
42
+ modules,
43
+ constraints: [],
44
+ decisions: [],
45
+ },
46
+ overwrite: {
47
+ mode: 'never',
48
+ requireConfirmation: true,
49
+ },
50
+ }
51
+ }
52
+
53
+ /** Infer path patterns from a list of files */
54
+ private inferPaths(files: string[]): string[] {
55
+ // Find common directory prefix
56
+ if (files.length === 0) return []
57
+
58
+ const dirs = new Set<string>()
59
+ for (const file of files) {
60
+ const parts = file.split('/')
61
+ parts.pop() // Remove filename
62
+ dirs.add(parts.join('/'))
63
+ }
64
+
65
+ // Use glob patterns for each unique directory
66
+ return [...dirs].map(dir => `${dir}/**`)
67
+ }
68
+
69
+ /** Find exported functions in a cluster — these are likely entry points */
70
+ private inferEntryFunctions(cluster: ModuleCluster, parsedFiles: ParsedFile[]): string[] {
71
+ const clusterFileSet = new Set(cluster.files)
72
+ const entryFunctions: string[] = []
73
+
74
+ for (const file of parsedFiles) {
75
+ if (!clusterFileSet.has(file.path)) continue
76
+ for (const fn of file.functions) {
77
+ if (fn.isExported) {
78
+ entryFunctions.push(fn.name)
79
+ }
80
+ }
81
+ }
82
+
83
+ return entryFunctions
84
+ }
85
+ }
@@ -0,0 +1,28 @@
1
+ import * as fs from 'node:fs/promises'
2
+ import { MikkContractSchema, type MikkContract } from './schema.js'
3
+ import { ContractNotFoundError } from '../utils/errors.js'
4
+
5
+ /**
6
+ * ContractReader — reads and validates mikk.json from disk.
7
+ */
8
+ export class ContractReader {
9
+ /** Read and validate mikk.json */
10
+ async read(contractPath: string): Promise<MikkContract> {
11
+ let content: string
12
+ try {
13
+ content = await fs.readFile(contractPath, 'utf-8')
14
+ } catch {
15
+ throw new ContractNotFoundError(contractPath)
16
+ }
17
+
18
+ const json = JSON.parse(content)
19
+ const result = MikkContractSchema.safeParse(json)
20
+
21
+ if (!result.success) {
22
+ const errors = result.error.issues.map(i => ` ${i.path.join('.')}: ${i.message}`).join('\n')
23
+ throw new Error(`Invalid mikk.json:\n${errors}`)
24
+ }
25
+
26
+ return result.data
27
+ }
28
+ }
@@ -0,0 +1,114 @@
1
+ import * as fs from 'node:fs/promises'
2
+ import * as path from 'node:path'
3
+ import type { MikkContract } from './schema.js'
4
+ import { hashContent } from '../hash/file-hasher.js'
5
+
6
+ const VERSION = 'mikk-cli@1.0.0'
7
+
8
+ export interface UpdateResult {
9
+ updated: boolean
10
+ reason?: string
11
+ requiresConfirmation?: boolean
12
+ proposedChanges?: object
13
+ }
14
+
15
+ /**
16
+ * ContractWriter — writes mikk.json to disk.
17
+ * Implements the permission model (never / ask / explicit).
18
+ */
19
+ export class ContractWriter {
20
+ /** Write a new mikk.json — safe for first write */
21
+ async writeNew(contract: MikkContract, outputPath: string): Promise<void> {
22
+ await fs.mkdir(path.dirname(outputPath), { recursive: true })
23
+ const json = JSON.stringify(contract, null, 2)
24
+ await fs.writeFile(outputPath, json, 'utf-8')
25
+ }
26
+
27
+ /** Update an existing mikk.json respecting overwrite mode */
28
+ async update(
29
+ existing: MikkContract,
30
+ updates: Partial<MikkContract>,
31
+ outputPath: string
32
+ ): Promise<UpdateResult> {
33
+ const mode = existing.overwrite?.mode ?? 'never'
34
+
35
+ if (mode === 'never') {
36
+ return { updated: false, reason: 'overwrite mode is never' }
37
+ }
38
+
39
+ if (mode === 'ask') {
40
+ return {
41
+ updated: false,
42
+ requiresConfirmation: true,
43
+ proposedChanges: this.diffContracts(existing, updates),
44
+ }
45
+ }
46
+
47
+ if (mode === 'explicit') {
48
+ const updated = this.mergeContracts(existing, updates)
49
+ updated.overwrite = {
50
+ ...updated.overwrite,
51
+ lastOverwrittenBy: VERSION,
52
+ lastOverwrittenAt: new Date().toISOString(),
53
+ }
54
+ await this.writeNew(updated, outputPath)
55
+ await this.writeAuditLog(existing, updated, outputPath)
56
+ return { updated: true }
57
+ }
58
+
59
+ return { updated: false, reason: 'unknown mode' }
60
+ }
61
+
62
+ /** Merge two contracts — updates overwrite existing fields */
63
+ private mergeContracts(existing: MikkContract, updates: Partial<MikkContract>): MikkContract {
64
+ return {
65
+ ...existing,
66
+ ...updates,
67
+ declared: {
68
+ ...existing.declared,
69
+ ...(updates.declared || {}),
70
+ modules: updates.declared?.modules || existing.declared.modules,
71
+ constraints: updates.declared?.constraints || existing.declared.constraints,
72
+ decisions: updates.declared?.decisions || existing.declared.decisions,
73
+ },
74
+ project: {
75
+ ...existing.project,
76
+ ...(updates.project || {}),
77
+ },
78
+ }
79
+ }
80
+
81
+ /** Compute diff between two contracts */
82
+ private diffContracts(existing: MikkContract, updates: Partial<MikkContract>): object {
83
+ return {
84
+ before: existing.declared,
85
+ after: updates.declared || existing.declared,
86
+ }
87
+ }
88
+
89
+ /** Write audit log to .mikk/overwrite-history.json */
90
+ private async writeAuditLog(
91
+ before: MikkContract,
92
+ after: MikkContract,
93
+ contractPath: string
94
+ ): Promise<void> {
95
+ const historyPath = path.join(path.dirname(contractPath), '.mikk', 'overwrite-history.json')
96
+ let history: object[] = []
97
+
98
+ try {
99
+ const existing = await fs.readFile(historyPath, 'utf-8')
100
+ history = JSON.parse(existing)
101
+ } catch {
102
+ // No history file yet
103
+ }
104
+
105
+ history.push({
106
+ timestamp: new Date().toISOString(),
107
+ before: before.declared,
108
+ after: after.declared,
109
+ })
110
+
111
+ await fs.mkdir(path.dirname(historyPath), { recursive: true })
112
+ await fs.writeFile(historyPath, JSON.stringify(history, null, 2), 'utf-8')
113
+ }
114
+ }
@@ -0,0 +1,12 @@
1
+ export {
2
+ MikkContractSchema, MikkLockSchema,
3
+ MikkModuleSchema, MikkDecisionSchema, MikkOverwriteSchema,
4
+ MikkLockFunctionSchema, MikkLockModuleSchema, MikkLockFileSchema,
5
+ type MikkContract, type MikkLock, type MikkModule, type MikkDecision,
6
+ type MikkLockFunction, type MikkLockModule, type MikkLockFile,
7
+ } from './schema.js'
8
+ export { LockCompiler } from './lock-compiler.js'
9
+ export { ContractWriter, type UpdateResult } from './contract-writer.js'
10
+ export { ContractReader } from './contract-reader.js'
11
+ export { LockReader } from './lock-reader.js'
12
+ export { ContractGenerator } from './contract-generator.js'
@@ -0,0 +1,221 @@
1
+ import * as path from 'node:path'
2
+ import { createHash } from 'node:crypto'
3
+ import type { MikkContract, MikkLock } from './schema.js'
4
+ import type { DependencyGraph } from '../graph/types.js'
5
+ import type { ParsedFile } from '../parser/types.js'
6
+ import { hashContent } from '../hash/file-hasher.js'
7
+ import { computeModuleHash, computeRootHash } from '../hash/tree-hasher.js'
8
+ import { minimatch } from '../utils/minimatch.js'
9
+
10
+ const VERSION = 'mikk-cli@1.0.0'
11
+
12
+ /**
13
+ * LockCompiler — takes a DependencyGraph and a MikkContract
14
+ * and compiles the complete mikk.lock.json.
15
+ */
16
+ export class LockCompiler {
17
+ /** Main entry — compile full lock from graph + contract + parsed files */
18
+ compile(
19
+ graph: DependencyGraph,
20
+ contract: MikkContract,
21
+ parsedFiles: ParsedFile[]
22
+ ): MikkLock {
23
+ const functions = this.compileFunctions(graph, contract)
24
+ const classes = this.compileClasses(graph, contract)
25
+ const generics = this.compileGenerics(graph, contract)
26
+ const modules = this.compileModules(contract, parsedFiles)
27
+ const files = this.compileFiles(parsedFiles, contract)
28
+
29
+ const moduleHashes: Record<string, string> = {}
30
+ for (const [id, mod] of Object.entries(modules)) {
31
+ moduleHashes[id] = mod.hash
32
+ }
33
+
34
+ const lockData: MikkLock = {
35
+ version: '1.0.0',
36
+ generatedAt: new Date().toISOString(),
37
+ generatorVersion: VERSION,
38
+ projectRoot: contract.project.name,
39
+ syncState: {
40
+ status: 'clean',
41
+ lastSyncAt: new Date().toISOString(),
42
+ lockHash: '',
43
+ contractHash: hashContent(JSON.stringify(contract)),
44
+ },
45
+ modules,
46
+ functions,
47
+ classes: Object.keys(classes).length > 0 ? classes : undefined,
48
+ generics: Object.keys(generics).length > 0 ? generics : undefined,
49
+ files,
50
+ graph: {
51
+ nodes: graph.nodes.size,
52
+ edges: graph.edges.length,
53
+ rootHash: computeRootHash(moduleHashes),
54
+ },
55
+ }
56
+
57
+ // Compute overall lock hash from the compiled data
58
+ lockData.syncState.lockHash = hashContent(JSON.stringify({
59
+ functions: lockData.functions,
60
+ classes: lockData.classes,
61
+ generics: lockData.generics,
62
+ modules: lockData.modules,
63
+ files: lockData.files,
64
+ }))
65
+
66
+ return lockData
67
+ }
68
+
69
+ /** Compile function entries, assigning each to its module */
70
+ private compileFunctions(
71
+ graph: DependencyGraph,
72
+ contract: MikkContract
73
+ ): Record<string, MikkLock['functions'][string]> {
74
+ const result: Record<string, MikkLock['functions'][string]> = {}
75
+
76
+ for (const [id, node] of graph.nodes) {
77
+ if (node.type !== 'function') continue
78
+
79
+ const moduleId = this.findModule(node.file, contract.declared.modules)
80
+ const inEdges = graph.inEdges.get(id) || []
81
+ const outEdges = graph.outEdges.get(id) || []
82
+
83
+ result[id] = {
84
+ id,
85
+ name: node.label,
86
+ file: node.file,
87
+ startLine: node.metadata.startLine ?? 0,
88
+ endLine: node.metadata.endLine ?? 0,
89
+ hash: node.metadata.hash ?? '',
90
+ calls: outEdges.filter(e => e.type === 'calls').map(e => e.target),
91
+ calledBy: inEdges.filter(e => e.type === 'calls').map(e => e.source),
92
+ moduleId: moduleId || 'unknown',
93
+ purpose: node.metadata.purpose,
94
+ edgeCasesHandled: node.metadata.edgeCasesHandled,
95
+ errorHandling: node.metadata.errorHandling,
96
+ detailedLines: node.metadata.detailedLines,
97
+ }
98
+ }
99
+
100
+ return result
101
+ }
102
+
103
+ private compileClasses(
104
+ graph: DependencyGraph,
105
+ contract: MikkContract
106
+ ): Record<string, any> {
107
+ const result: Record<string, any> = {}
108
+ for (const [id, node] of graph.nodes) {
109
+ if (node.type !== 'class') continue
110
+ const moduleId = this.findModule(node.file, contract.declared.modules)
111
+ result[id] = {
112
+ id,
113
+ name: node.label,
114
+ file: node.file,
115
+ startLine: node.metadata.startLine ?? 0,
116
+ endLine: node.metadata.endLine ?? 0,
117
+ moduleId: moduleId || 'unknown',
118
+ isExported: node.metadata.isExported ?? false,
119
+ purpose: node.metadata.purpose,
120
+ edgeCasesHandled: node.metadata.edgeCasesHandled,
121
+ errorHandling: node.metadata.errorHandling,
122
+ }
123
+ }
124
+ return result
125
+ }
126
+
127
+ private compileGenerics(
128
+ graph: DependencyGraph,
129
+ contract: MikkContract
130
+ ): Record<string, any> {
131
+ const result: Record<string, any> = {}
132
+ for (const [id, node] of graph.nodes) {
133
+ if (node.type !== 'generic') continue
134
+ const moduleId = this.findModule(node.file, contract.declared.modules)
135
+ result[id] = {
136
+ id,
137
+ name: node.label,
138
+ type: node.metadata.hash ?? 'generic', // we stored type name in hash
139
+ file: node.file,
140
+ startLine: node.metadata.startLine ?? 0,
141
+ endLine: node.metadata.endLine ?? 0,
142
+ moduleId: moduleId || 'unknown',
143
+ isExported: node.metadata.isExported ?? false,
144
+ purpose: node.metadata.purpose,
145
+ }
146
+ }
147
+ return result
148
+ }
149
+
150
+ /** Compile module entries from contract definitions */
151
+ private compileModules(
152
+ contract: MikkContract,
153
+ parsedFiles: ParsedFile[]
154
+ ): Record<string, MikkLock['modules'][string]> {
155
+ const result: Record<string, MikkLock['modules'][string]> = {}
156
+
157
+ for (const module of contract.declared.modules) {
158
+ const moduleFiles = parsedFiles
159
+ .filter(f => this.fileMatchesModule(f.path, module.paths))
160
+ .map(f => f.path)
161
+
162
+ const fileHashes = moduleFiles.map(f => {
163
+ const parsed = parsedFiles.find(pf => pf.path === f)
164
+ return parsed?.hash ?? ''
165
+ })
166
+
167
+ result[module.id] = {
168
+ id: module.id,
169
+ files: moduleFiles,
170
+ hash: computeModuleHash(fileHashes),
171
+ fragmentPath: `.mikk/fragments/${module.id}.lock`,
172
+ }
173
+ }
174
+
175
+ return result
176
+ }
177
+
178
+ /** Compile file entries */
179
+ private compileFiles(
180
+ parsedFiles: ParsedFile[],
181
+ contract: MikkContract
182
+ ): Record<string, MikkLock['files'][string]> {
183
+ const result: Record<string, MikkLock['files'][string]> = {}
184
+
185
+ for (const file of parsedFiles) {
186
+ const moduleId = this.findModule(file.path, contract.declared.modules)
187
+ result[file.path] = {
188
+ path: file.path,
189
+ hash: file.hash,
190
+ moduleId: moduleId || 'unknown',
191
+ lastModified: new Date().toISOString(),
192
+ }
193
+ }
194
+
195
+ return result
196
+ }
197
+
198
+ /** Find which module a file belongs to based on path patterns */
199
+ private findModule(
200
+ filePath: string,
201
+ modules: MikkContract['declared']['modules']
202
+ ): string | null {
203
+ for (const module of modules) {
204
+ if (this.fileMatchesModule(filePath, module.paths)) {
205
+ return module.id
206
+ }
207
+ }
208
+ return null
209
+ }
210
+
211
+ /** Check if a file path matches any of the module's path patterns */
212
+ private fileMatchesModule(filePath: string, patterns: string[]): boolean {
213
+ const normalized = filePath.replace(/\\/g, '/')
214
+ for (const pattern of patterns) {
215
+ if (minimatch(normalized, pattern)) {
216
+ return true
217
+ }
218
+ }
219
+ return false
220
+ }
221
+ }
@@ -0,0 +1,34 @@
1
+ import * as fs from 'node:fs/promises'
2
+ import { MikkLockSchema, type MikkLock } from './schema.js'
3
+ import { LockNotFoundError } from '../utils/errors.js'
4
+
5
+ /**
6
+ * LockReader — reads and validates mikk.lock.json from disk.
7
+ */
8
+ export class LockReader {
9
+ /** Read and validate mikk.lock.json */
10
+ async read(lockPath: string): Promise<MikkLock> {
11
+ let content: string
12
+ try {
13
+ content = await fs.readFile(lockPath, 'utf-8')
14
+ } catch {
15
+ throw new LockNotFoundError()
16
+ }
17
+
18
+ const json = JSON.parse(content)
19
+ const result = MikkLockSchema.safeParse(json)
20
+
21
+ if (!result.success) {
22
+ const errors = result.error.issues.map(i => ` ${i.path.join('.')}: ${i.message}`).join('\n')
23
+ throw new Error(`Invalid mikk.lock.json:\n${errors}`)
24
+ }
25
+
26
+ return result.data
27
+ }
28
+
29
+ /** Write lock file to disk */
30
+ async write(lock: MikkLock, lockPath: string): Promise<void> {
31
+ const json = JSON.stringify(lock, null, 2)
32
+ await fs.writeFile(lockPath, json, 'utf-8')
33
+ }
34
+ }
@@ -0,0 +1,147 @@
1
+ import { z } from 'zod'
2
+
3
+ // ─── mikk.json schema ──────────────────────────────────────
4
+
5
+ export const MikkModuleSchema = z.object({
6
+ id: z.string(),
7
+ name: z.string(),
8
+ description: z.string(),
9
+ intent: z.string().optional(),
10
+ owners: z.array(z.string()).optional(),
11
+ paths: z.array(z.string()),
12
+ entryFunctions: z.array(z.string()).optional(),
13
+ })
14
+
15
+ export const MikkDecisionSchema = z.object({
16
+ id: z.string(),
17
+ title: z.string(),
18
+ reason: z.string(),
19
+ date: z.string(),
20
+ })
21
+
22
+ export const MikkOverwriteSchema = z.object({
23
+ mode: z.enum(['never', 'ask', 'explicit']).default('never'),
24
+ requireConfirmation: z.boolean().default(true),
25
+ lastOverwrittenBy: z.string().optional(),
26
+ lastOverwrittenAt: z.string().optional(),
27
+ }).default({ mode: 'never', requireConfirmation: true })
28
+
29
+ export const MikkContractSchema = z.object({
30
+ version: z.string(),
31
+ project: z.object({
32
+ name: z.string(),
33
+ description: z.string(),
34
+ language: z.string(),
35
+ framework: z.string().optional(),
36
+ entryPoints: z.array(z.string()),
37
+ }),
38
+ declared: z.object({
39
+ modules: z.array(MikkModuleSchema),
40
+ constraints: z.array(z.string()).default([]),
41
+ decisions: z.array(MikkDecisionSchema).default([]),
42
+ }),
43
+ overwrite: MikkOverwriteSchema,
44
+ })
45
+
46
+ export type MikkContract = z.infer<typeof MikkContractSchema>
47
+ export type MikkModule = z.infer<typeof MikkModuleSchema>
48
+ export type MikkDecision = z.infer<typeof MikkDecisionSchema>
49
+
50
+ // ─── mikk.lock.json schema ─────────────────────────────────
51
+
52
+ export const MikkLockFunctionSchema = z.object({
53
+ id: z.string(),
54
+ name: z.string(),
55
+ file: z.string(),
56
+ startLine: z.number(),
57
+ endLine: z.number(),
58
+ hash: z.string(),
59
+ calls: z.array(z.string()),
60
+ calledBy: z.array(z.string()),
61
+ moduleId: z.string(),
62
+ purpose: z.string().optional(),
63
+ edgeCasesHandled: z.array(z.string()).optional(),
64
+ errorHandling: z.array(z.object({
65
+ line: z.number(),
66
+ type: z.enum(['try-catch', 'throw']),
67
+ detail: z.string(),
68
+ })).optional(),
69
+ detailedLines: z.array(z.object({
70
+ startLine: z.number(),
71
+ endLine: z.number(),
72
+ blockType: z.string(),
73
+ })).optional()
74
+ })
75
+
76
+ export const MikkLockModuleSchema = z.object({
77
+ id: z.string(),
78
+ files: z.array(z.string()),
79
+ hash: z.string(),
80
+ fragmentPath: z.string(),
81
+ })
82
+
83
+ export const MikkLockFileSchema = z.object({
84
+ path: z.string(),
85
+ hash: z.string(),
86
+ moduleId: z.string(),
87
+ lastModified: z.string(),
88
+ })
89
+
90
+ export const MikkLockClassSchema = z.object({
91
+ id: z.string(),
92
+ name: z.string(),
93
+ file: z.string(),
94
+ startLine: z.number(),
95
+ endLine: z.number(),
96
+ moduleId: z.string(),
97
+ isExported: z.boolean(),
98
+ purpose: z.string().optional(),
99
+ edgeCasesHandled: z.array(z.string()).optional(),
100
+ errorHandling: z.array(z.object({
101
+ line: z.number(),
102
+ type: z.enum(['try-catch', 'throw']),
103
+ detail: z.string(),
104
+ })).optional(),
105
+ })
106
+
107
+ export const MikkLockGenericSchema = z.object({
108
+ id: z.string(),
109
+ name: z.string(),
110
+ type: z.string(),
111
+ file: z.string(),
112
+ startLine: z.number(),
113
+ endLine: z.number(),
114
+ moduleId: z.string(),
115
+ isExported: z.boolean(),
116
+ purpose: z.string().optional(),
117
+ })
118
+
119
+ export const MikkLockSchema = z.object({
120
+ version: z.string(),
121
+ generatedAt: z.string(),
122
+ generatorVersion: z.string(),
123
+ projectRoot: z.string(),
124
+ syncState: z.object({
125
+ status: z.enum(['clean', 'syncing', 'drifted', 'conflict']),
126
+ lastSyncAt: z.string(),
127
+ lockHash: z.string(),
128
+ contractHash: z.string(),
129
+ }),
130
+ modules: z.record(MikkLockModuleSchema),
131
+ functions: z.record(MikkLockFunctionSchema),
132
+ classes: z.record(MikkLockClassSchema).optional(),
133
+ generics: z.record(MikkLockGenericSchema).optional(),
134
+ files: z.record(MikkLockFileSchema),
135
+ graph: z.object({
136
+ nodes: z.number(),
137
+ edges: z.number(),
138
+ rootHash: z.string(),
139
+ }),
140
+ })
141
+
142
+ export type MikkLock = z.infer<typeof MikkLockSchema>
143
+ export type MikkLockFunction = z.infer<typeof MikkLockFunctionSchema>
144
+ export type MikkLockModule = z.infer<typeof MikkLockModuleSchema>
145
+ export type MikkLockFile = z.infer<typeof MikkLockFileSchema>
146
+ export type MikkLockClass = z.infer<typeof MikkLockClassSchema>
147
+ export type MikkLockGeneric = z.infer<typeof MikkLockGenericSchema>