@getmikk/core 1.7.1 → 1.8.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.
package/README.md CHANGED
@@ -1,469 +1,139 @@
1
- # @getmikk/core
1
+ # @getmikk/core
2
2
 
3
- > The foundation of the Mikk ecosystem — TypeScript AST parsing, dependency graph construction, Merkle-tree hashing, contract management, and lock file compilation.
3
+ > AST parsing, dependency graph, Merkle hashing, contract management, boundary enforcement.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/@getmikk/core)](https://www.npmjs.com/package/@getmikk/core)
6
6
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](../../LICENSE)
7
7
 
8
- `@getmikk/core` is the foundation every other Mikk package builds on. It owns the complete pipeline for turning raw **TypeScript and Go** source into structured, queryable intelligence: parsing source files into real ASTs (TS Compiler API for TypeScript; regex + stateful scanning for Go; no external toolchain required), building a two-pass dependency graph with O(1) adjacency lookups, computing Merkle-tree SHA-256 hashes at function → file → module → root level, and compiling everything into a `mikk.lock.json` snapshot that every other package reads from.
8
+ Foundation package for the Mikk ecosystem. All other packages depend on core nothing in core depends on them.
9
9
 
10
- Every AI context query, impact analysis, contract validation, and diagram generation ultimately runs on the graph and lock file produced here.
11
-
12
- > Part of [Mikk](../../README.md) — the codebase nervous system for AI-assisted development.
10
+ > Part of [Mikk](../../README.md) live architectural context for your AI agent.
13
11
 
14
12
  ---
15
13
 
16
- ## Installation
14
+ ## What is in core
17
15
 
18
- ```bash
19
- npm install @getmikk/core
20
- # or
21
- bun add @getmikk/core
22
- ```
23
-
24
- ---
25
-
26
- ## Architecture Overview
27
-
28
- ```
29
- Source Files (.ts/.tsx/.go)
30
-
31
-
32
- ┌─────────┐
33
- │ Parser │ ← TypeScriptParser / GoParser
34
- └────┬────┘
35
- │ ParsedFile[]
36
-
37
- ┌──────────────┐
38
- │ GraphBuilder │ ← Two-pass: nodes → edges
39
- └──────┬───────┘
40
- │ DependencyGraph
41
-
42
- ┌────────────────┐
43
- │ LockCompiler │ ← Merkle-tree hashes
44
- └───────┬────────┘
45
- │ MikkLock
46
-
47
- ┌─────────────────┐
48
- │ ContractWriter │ ← Permission model (never/ask/explicit)
49
- └─────────────────┘
50
- ```
16
+ ### Parsers
51
17
 
52
- ---
18
+ Three language parsers, each following the same interface: `parse(filePath, content)` → `ParsedFile`.
53
19
 
54
- ## Modules
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.
55
22
 
56
- ### 1. Parser — Source Code Analysis
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.
57
25
 
58
- The parser module turns raw TypeScript/TSX files into structured `ParsedFile` objects using the TypeScript Compiler API.
26
+ **Go**
27
+ Regex + stateful scanning. No Go toolchain dependency. Extracts: functions, methods (with receiver types), structs, interfaces, package imports. `go.mod` used for project boundary detection.
59
28
 
60
- ```typescript
61
- import { TypeScriptParser, getParser, parseFiles } from '@getmikk/core'
29
+ ### GraphBuilder
62
30
 
63
- // Parse a single file
64
- const parser = new TypeScriptParser()
65
- const parsed = parser.parse('/src/utils/math.ts', fileContent)
31
+ Two-pass O(n) dependency graph construction:
32
+ 1. **Pass 1** create all nodes (functions, files)
33
+ 2. **Pass 2** — wire all edges (import edges, call edges, containment edges)
66
34
 
67
- console.log(parsed.functions) // ParsedFunction[] name, params, returnType, startLine, endLine, calls[]
68
- console.log(parsed.classes) // ParsedClass[] — name, methods[], properties[], decorators[]
69
- console.log(parsed.imports) // ParsedImport[] — source, specifiers, isTypeOnly
70
- console.log(parsed.exports) // ParsedExport[] — name, isDefault, isTypeOnly
71
- console.log(parsed.generics) // ParsedGeneric[] — interfaces, types, const declarations
35
+ Result: `DependencyGraph` with forward `outEdges` and reverse `inEdges` maps for O(1) lookups in both directions.
72
36
 
73
- // Factory — auto-selects parser by file extension
74
- const parser = getParser('component.tsx') // returns TypeScriptParser
37
+ ### ImpactAnalyzer
75
38
 
76
- // Batch parse with import resolution
77
- const files = await parseFiles(filePaths, projectRoot, readFileFn)
78
- // Returns ParsedFile[] with all import paths resolved to absolute paths
79
- ```
39
+ BFS backward walk from a set of changed nodes. Returns:
40
+ - `changed` directly modified nodes
41
+ - `impacted` all transitively affected upstream callers
42
+ - `classified` — impacted nodes sorted into `critical | high | medium | low` by proximity
43
+ - `depth` — max blast radius depth
44
+ - `confidence` — `high | medium | low` based on analysis mode
80
45
 
81
- #### TypeScriptExtractor
46
+ ### ClusterDetector
82
47
 
83
- The extractor walks the TypeScript AST and pulls out detailed metadata:
48
+ Groups files into logical modules via greedy agglomeration. Produces clusters with a `confidence` score (0–1). Used by `mikk init` to auto-generate `mikk.json` from an unknown codebase.
84
49
 
85
- - **Functions**: name, parameters (with types & defaults), return type, line range, internal calls, `async`/generator flags, decorators, type parameters
86
- - **Classes**: name, methods (with full function metadata), properties, decorators, `extends`/`implements`, type parameters
87
- - **Generics**: interfaces, type aliases, const declarations, enums
88
- - **Imports**: named, default, namespace, type-only imports
89
- - **Exports**: named, default, re-exports
50
+ ### BoundaryChecker
90
51
 
91
- #### TypeScriptResolver
52
+ Runs all declared constraint rules against the lock file. For each violation, returns: the source function, target function, which rule was violated, and severity. Used live by `mikk_before_edit` and `mikk ci`.
92
53
 
93
- Resolves import paths against the actual project filesystem:
54
+ **Constraint types:**
55
+ - `no-import` — module A must not import from module B
56
+ - `must-use` — module A must use dependency B
57
+ - `no-call` — specific functions must not call specific targets
58
+ - `layer` — layered architecture enforcement (can only import from lower-numbered layers)
59
+ - `naming` — function or file naming pattern via regex
60
+ - `max-files` — maximum file count per module
94
61
 
95
- ```typescript
96
- import { TypeScriptResolver } from '@getmikk/core'
62
+ ### Merkle Hashing
97
63
 
98
- const resolver = new TypeScriptResolver()
99
- // Resolves: relative paths, path aliases (tsconfig paths), index files, extension inference (.ts/.tsx/.js)
100
- const resolved = resolver.resolve(importDecl, fromFilePath, allProjectFiles)
64
+ SHA-256 at every level:
101
65
  ```
102
-
103
- #### Go Parser
104
-
105
- Parses `.go` files without requiring the Go toolchain:
106
-
107
- ```typescript
108
- import { GoParser } from '@getmikk/core'
109
-
110
- const parser = new GoParser()
111
- const parsed = parser.parse('service.go', fileContent)
112
-
113
- console.log(parsed.functions) // ParsedFunction[] — name, params with types, return type, calls[]
114
- console.log(parsed.classes) // ParsedClass[] — receiver-based methods grouped by type name
115
- console.log(parsed.imports) // ParsedImport[] — resolved against go.mod module path
116
- console.log(parsed.exports) // ParsedExport[] — uppercase identifiers (Go convention)
117
- console.log(parsed.routes) // ParsedRoute[] — Gin, Echo, Chi, Mux, net/http routes
66
+ function hash → file hash → module hash → root hash
118
67
  ```
119
68
 
120
- **Features**:
121
- - Stateful line/character scanning (handles strings, comments, nested braces correctly)
122
- - Receiver methods grouped with struct types as classes
123
- - HTTP route detection (Gin/Echo/Chi/Mux/net.http/Fiber patterns)
124
- - Error handling detection (`if err != nil` patterns)
125
- - Function call extraction from bodies
126
- - Grouped parameter expansion (`first, last string` → both typed as `string`)
127
- - Import resolution via `go.mod` module path
69
+ One root hash comparison = instant full drift detection. Persisted in SQLite with WAL mode for zero-contention concurrent reads.
128
70
 
129
- #### Auto-detection by file extension
71
+ ### LockCompiler
130
72
 
131
- ```typescript
132
- const parser = getParser('file.ts') // → TypeScriptParser
133
- const parser = getParser('service.go') // → GoParser
134
- ```
135
-
136
- ---
73
+ 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.
137
74
 
138
- ### 2. Graph — Dependency Graph Construction
75
+ Lock format v1.7.0:
76
+ - Integer-based function index (`fnIndex`) — call graph edges stored as integer references, not repeated strings
77
+ - Compact JSON output — no pretty-printing
78
+ - Backward-compatible hydration for older formats
139
79
 
140
- The graph module builds a complete dependency graph from parsed files.
80
+ ### ContractReader / ContractWriter / LockReader
141
81
 
142
- ```typescript
143
- import { GraphBuilder, ImpactAnalyzer, ClusterDetector } from '@getmikk/core'
144
-
145
- // Build the graph
146
- const builder = new GraphBuilder()
147
- const graph = builder.build(parsedFiles)
148
-
149
- console.log(graph.nodes) // Map<string, GraphNode> — file, function, class, generic nodes
150
- console.log(graph.edges) // GraphEdge[] — import, call, containment, implements edges
151
- console.log(graph.adjacency) // Map<string, string[]> — forward adjacency
152
- console.log(graph.reverse) // Map<string, string[]> — reverse adjacency
153
- ```
82
+ Read and write `mikk.json` and `mikk.lock.json`. `LockReader.write()` uses atomic temp-file + rename to prevent corruption.
154
83
 
155
- #### GraphBuilder
84
+ ### AdrManager
156
85
 
157
- Two-pass construction:
158
- 1. **Pass 1 — Nodes**: Creates nodes for every file, function, class, and generic declaration
159
- 2. **Pass 2 — Edges**: Creates edges for imports, function calls, class containment, and cross-file references
86
+ 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.
160
87
 
161
- Node types: `file`, `function`, `class`, `generic`
162
- Edge types: `import`, `call`, `containment`, `implements`
88
+ ### DeadCodeDetector
163
89
 
164
- #### ImpactAnalyzer
90
+ Identifies functions with zero callers after exempting: exported functions, entry points, detected route handlers, test functions, and constructors. Returns per-module breakdown.
165
91
 
166
- BFS backward walk to find everything affected by a change:
167
-
168
- ```typescript
169
- const analyzer = new ImpactAnalyzer(graph)
170
- const impact = analyzer.analyze(['src/utils/math.ts::calculateTotal'])
171
-
172
- console.log(impact.changed) // string[] — directly changed node IDs
173
- console.log(impact.impacted) // string[] — transitively affected nodes
174
- console.log(impact.depth) // number — max propagation depth
175
- console.log(impact.confidence) // 'high' | 'medium' | 'low'
176
- console.log(impact.classified) // { critical: [], high: [], medium: [], low: [] }
177
- ```
178
-
179
- #### ClusterDetector
180
-
181
- Greedy agglomeration algorithm for automatic module discovery:
182
-
183
- ```typescript
184
- const detector = new ClusterDetector(graph, /* minClusterSize */ 3, /* minCouplingScore */ 0.1)
185
- const clusters = detector.detect()
186
-
187
- // Returns ModuleCluster[] with:
188
- // - id, label (auto-generated from common paths)
189
- // - nodeIds[] — functions/classes in this cluster
190
- // - cohesion — internal coupling score (0-1)
191
- // - coupling — Map<clusterId, score> — external coupling
192
- ```
92
+ ### Route Detection
193
93
 
194
- The algorithm starts with one cluster per file, then iteratively merges the pair with the highest coupling score until no pair exceeds the threshold.
94
+ Detects HTTP route definitions in Express, Koa, and Hono patterns. Extracts: HTTP method, path string, handler function reference, middleware chain, file, and line number.
195
95
 
196
96
  ---
197
97
 
198
- ### 3. Contract — mikk.json & mikk.lock.json
199
-
200
- The contract module manages the two core Mikk files using Zod validation.
201
-
202
- #### Schemas
203
-
204
- All schemas are exported as Zod objects for runtime validation:
98
+ ## Key Types
205
99
 
206
100
  ```typescript
207
- import {
208
- MikkContractSchema, // mikk.json validation
209
- MikkLockSchema, // mikk.lock.json validation
210
- MikkModuleSchema, // Module definition
211
- MikkDecisionSchema, // Architecture decision record
212
- } from '@getmikk/core'
213
-
214
- // Validate a contract
215
- const result = MikkContractSchema.safeParse(rawJson)
216
- if (!result.success) console.error(result.error.issues)
217
- ```
218
-
219
- #### mikk.json (Contract)
220
-
221
- Defines the project's architectural rules:
222
-
223
- ```typescript
224
- type MikkContract = {
225
- name: string
226
- version: string
227
- modules: Record<string, MikkModule> // Module definitions with intent, public API, constraints
228
- decisions: MikkDecision[] // Architecture Decision Records (ADRs)
229
- overwrite: {
230
- permission: 'never' | 'ask' | 'explicit'
231
- lastOverwrittenBy?: string
232
- lastOverwrittenAt?: string
233
- }
234
- }
235
- ```
236
-
237
- #### mikk.lock.json (Lock File)
238
-
239
- Auto-generated snapshot of the entire codebase:
240
-
241
- ```typescript
242
- type MikkLock = {
243
- generatorVersion: string
244
- generatedAt: string
245
- rootHash: string // Merkle root of entire project
246
- modules: Record<string, MikkLockModule>
247
- }
248
-
249
- type MikkLockModule = {
250
- hash: string // Merkle hash of all files in module
251
- files: Record<string, MikkLockFile>
252
- }
253
-
254
- type MikkLockFile = {
101
+ interface ParsedFile {
102
+ path: string
255
103
  hash: string
256
- functions: Record<string, MikkLockFunction>
257
- classes: Record<string, MikkLockClass>
258
- generics: Record<string, MikkLockGeneric>
104
+ language: string
105
+ functions: ParsedFunction[]
106
+ imports: ParsedImport[]
107
+ exports: ParsedExport[]
108
+ classes: ParsedClass[]
109
+ routes: ParsedRoute[]
259
110
  }
260
- ```
261
-
262
- #### ContractReader / LockReader
263
-
264
- ```typescript
265
- import { ContractReader, LockReader } from '@getmikk/core'
266
-
267
- const contractReader = new ContractReader()
268
- const contract = await contractReader.read('./mikk.json')
269
-
270
- const lockReader = new LockReader()
271
- const lock = await lockReader.read('./mikk.lock.json')
272
- await lockReader.write(updatedLock, './mikk.lock.json')
273
- ```
274
-
275
- #### ContractWriter — Permission Model
276
-
277
- ```typescript
278
- import { ContractWriter } from '@getmikk/core'
279
-
280
- const writer = new ContractWriter()
281
-
282
- // First-time write
283
- await writer.writeNew(contract, './mikk.json')
284
-
285
- // Update with permission model
286
- const result = await writer.update(existingContract, updates, './mikk.json')
287
- // result.updated — boolean
288
- // result.requiresConfirmation — true if permission is 'ask'
289
- // result.proposedChanges — diff object when confirmation needed
290
- ```
291
-
292
- Permission levels:
293
- - **`never`** — Contract is read-only, updates are rejected
294
- - **`ask`** — Returns `requiresConfirmation: true` with proposed changes
295
- - **`explicit`** — Auto-applies updates with audit trail
296
111
 
297
- #### LockCompiler
298
-
299
- Compiles the full lock file from graph + contract + parsed files:
300
-
301
- ```typescript
302
- import { LockCompiler } from '@getmikk/core'
303
-
304
- const compiler = new LockCompiler()
305
- const lock = compiler.compile(graph, contract, parsedFiles)
306
- // Computes Merkle-tree hashes at every level:
307
- // function → file → module → root
308
- ```
309
-
310
- #### ContractGenerator
311
-
312
- Auto-generates a `mikk.json` skeleton from detected clusters:
313
-
314
- ```typescript
315
- import { ContractGenerator } from '@getmikk/core'
316
-
317
- const generator = new ContractGenerator()
318
- const contract = generator.generateFromClusters(clusters, parsedFiles, 'my-project')
319
- ```
320
-
321
- #### BoundaryChecker
322
-
323
- CI-ready enforcement layer:
324
-
325
- ```typescript
326
- import { BoundaryChecker } from '@getmikk/core'
327
-
328
- const checker = new BoundaryChecker(contract, lock)
329
- const result = checker.check()
330
-
331
- if (!result.pass) {
332
- for (const v of result.violations) {
333
- console.error(`${v.severity}: ${v.message}`)
334
- // severity: 'error' | 'warning'
335
- // type: 'boundary-crossing' | 'constraint-violation'
336
- }
337
- process.exit(1)
112
+ interface DependencyGraph {
113
+ nodes: Map<string, GraphNode>
114
+ edges: GraphEdge[]
115
+ outEdges: Map<string, GraphEdge[]>
116
+ inEdges: Map<string, GraphEdge[]>
338
117
  }
339
- ```
340
-
341
- ---
342
-
343
- ### 4. Hash — Merkle-Tree Integrity
344
-
345
- ```typescript
346
- import { hashContent, hashFile, hashFunctionBody, computeModuleHash, computeRootHash } from '@getmikk/core'
347
-
348
- // Hash raw content (SHA-256)
349
- const h1 = hashContent('function foo() {}')
350
-
351
- // Hash a file from disk
352
- const h2 = await hashFile('/src/index.ts')
353
-
354
- // Hash a specific line range (function body)
355
- const h3 = hashFunctionBody(fileContent, 10, 25)
356
-
357
- // Merkle tree
358
- const moduleHash = computeModuleHash(['fileHash1', 'fileHash2'])
359
- const rootHash = computeRootHash(['moduleHash1', 'moduleHash2'])
360
- ```
361
-
362
- #### HashStore — SQLite Persistence
363
-
364
- ```typescript
365
- import { HashStore } from '@getmikk/core'
366
-
367
- const store = new HashStore('/project/.mikk/hashes.db')
368
-
369
- store.set('src/index.ts', 'abc123...', 4096)
370
- const entry = store.get('src/index.ts')
371
- // { path, hash, size, updatedAt }
372
-
373
- const changed = store.getChangedSince(Date.now() - 60_000)
374
- store.delete('src/old-file.ts')
375
-
376
- // Batch operations use SQLite transactions for performance
377
- const allPaths = store.getAllPaths()
378
- ```
379
118
 
380
- Uses SQLite in WAL mode for concurrent read access and fast writes.
381
-
382
- ---
383
-
384
- ### 5. Utilities
385
-
386
- #### Error Hierarchy
387
-
388
- ```typescript
389
- import {
390
- MikkError, // Base error class
391
- ParseError, // File parsing failures
392
- ContractNotFoundError, // mikk.json not found
393
- LockNotFoundError, // mikk.lock.json not found
394
- UnsupportedLanguageError, // Unsupported file extension
395
- OverwritePermissionError, // Contract overwrite denied
396
- SyncStateError, // Lock file out of sync
397
- } from '@getmikk/core'
398
- ```
399
-
400
- #### Logging
401
-
402
- ```typescript
403
- import { logger, setLogLevel } from '@getmikk/core'
404
-
405
- setLogLevel('debug') // 'debug' | 'info' | 'warn' | 'error' | 'silent'
406
-
407
- logger.info('Analysis complete', { files: 42, duration: '1.2s' })
408
- // Outputs structured JSON to stderr
409
- ```
410
-
411
- #### File Utilities
412
-
413
- ```typescript
414
- import { discoverFiles, readFileContent, writeFileContent, fileExists, setupMikkDirectory } from '@getmikk/core'
415
-
416
- // Discover TypeScript files
417
- const files = await discoverFiles('/project', ['src/**/*.ts'], ['node_modules'])
418
-
419
- // Read/write
420
- const content = await readFileContent('/src/index.ts')
421
- await writeFileContent('/output/result.json', jsonString) // auto-creates directories
422
-
423
- // Initialize .mikk/ directory structure
424
- await setupMikkDirectory('/project')
425
- ```
426
-
427
- #### Fuzzy Matching
428
-
429
- ```typescript
430
- import { scoreFunctions, findFuzzyMatches, levenshtein } from '@getmikk/core'
431
-
432
- // Score lock file functions against a natural-language prompt
433
- const matches = scoreFunctions('calculate the total price', lock, 10)
434
- // Returns FuzzyMatch[] sorted by relevance score
435
-
436
- // "Did you mean?" suggestions
437
- const suggestions = findFuzzyMatches('calcualteTotal', lock, 5)
438
- // Returns closest function names by Levenshtein distance
439
-
440
- // Raw edit distance
441
- const dist = levenshtein('kitten', 'sitting') // 3
119
+ interface MikkLock {
120
+ version: string
121
+ lockDate: string
122
+ project: { name: string; language: string }
123
+ fnIndex: string[] // all function IDs — edges reference by integer index
124
+ functions: Record<string, LockFunction>
125
+ files: Record<string, LockFile>
126
+ routes: LockRoute[]
127
+ syncState: { status: string; lastUpdated: number }
128
+ }
442
129
  ```
443
130
 
444
131
  ---
445
132
 
446
- ## Types
133
+ ## Test Coverage
447
134
 
448
- All types are exported and can be imported directly:
135
+ 196 tests across: TypeScript parser, JavaScript parser, Go parser, dependency graph, impact analysis, hash store, contract validation, dead code detection, fuzzy matching, filesystem utilities.
449
136
 
450
- ```typescript
451
- import type {
452
- // Parser
453
- ParsedFile, ParsedFunction, ParsedClass, ParsedImport, ParsedExport, ParsedParam, ParsedGeneric,
454
- // Graph
455
- DependencyGraph, GraphNode, GraphEdge, ImpactResult, NodeType, EdgeType, ModuleCluster, ClassifiedImpact, RiskLevel,
456
- // Contract
457
- MikkContract, MikkLock, MikkModule, MikkDecision, MikkLockFunction, MikkLockModule, MikkLockFile,
458
- // Boundary
459
- BoundaryViolation, BoundaryCheckResult, ViolationSeverity,
460
- // Writer
461
- UpdateResult,
462
- } from '@getmikk/core'
137
+ ```bash
138
+ bun test
463
139
  ```
464
-
465
- ---
466
-
467
- ## License
468
-
469
- [Apache-2.0](../../LICENSE)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getmikk/core",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -24,6 +24,8 @@
24
24
  "@types/better-sqlite3": "^7.6.13",
25
25
  "better-sqlite3": "^12.6.2",
26
26
  "fast-glob": "^3.3.0",
27
+ "tree-sitter-wasms": "^0.1.13",
28
+ "web-tree-sitter": "0.20.8",
27
29
  "zod": "^3.22.0"
28
30
  },
29
31
  "devDependencies": {
@@ -3,7 +3,7 @@ import { MikkContractSchema, type MikkContract } from './schema.js'
3
3
  import { ContractNotFoundError } from '../utils/errors.js'
4
4
 
5
5
  /**
6
- * ContractReader reads and validates mikk.json from disk.
6
+ * ContractReader -- reads and validates mikk.json from disk.
7
7
  */
8
8
  export class ContractReader {
9
9
  /** Read and validate mikk.json */
@@ -15,7 +15,7 @@ export class ContractReader {
15
15
  throw new ContractNotFoundError(contractPath)
16
16
  }
17
17
 
18
- const json = JSON.parse(content)
18
+ const json = JSON.parse(content.replace(/^\uFEFF/, ''))
19
19
  const result = MikkContractSchema.safeParse(json)
20
20
 
21
21
  if (!result.success) {
@@ -1,4 +1,4 @@
1
- import * as path from 'node:path'
1
+ import * as path from 'node:path'
2
2
  import { createHash } from 'node:crypto'
3
3
  import type { MikkContract, MikkLock } from './schema.js'
4
4
  import type { DependencyGraph } from '../graph/types.js'
@@ -10,22 +10,22 @@ import { minimatch } from '../utils/minimatch.js'
10
10
 
11
11
  const VERSION = '@getmikk/cli@1.2.1'
12
12
 
13
- // ─── Heuristic purpose inference ────────────────────────────────────
13
+ // Heuristic purpose inference
14
14
  // When JSDoc is missing we derive a short purpose string from:
15
- // 1. camelCase / PascalCase function name natural language
15
+ // 1. camelCase / PascalCase function name -> natural language
16
16
  // 2. parameter names (context clue)
17
17
  // 3. return type (if present)
18
18
  //
19
19
  // Examples:
20
- // "getUserProjectRole" + params:["userId","projectId"] "Get user project role (userId, projectId)"
21
- // "DashboardPage" + returnType:"JSX.Element" "Dashboard page component"
22
- // ────────────────────────────────────────────────────────────────────
20
+ // "getUserProjectRole" + params:["userId","projectId"] -> "Get user project role (userId, projectId)"
21
+ // "DashboardPage" + returnType:"JSX.Element" -> "Dashboard page component"
22
+ //
23
23
 
24
24
  /** Split camelCase/PascalCase identifier into lowercase words */
25
25
  function splitIdentifier(name: string): string[] {
26
26
  return name
27
27
  .replace(/([a-z0-9])([A-Z])/g, '$1 $2') // camelCase boundary
28
- .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2') // ABCDef ABC Def
28
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2') // ABCDef -> ABC Def
29
29
  .split(/[\s_-]+/)
30
30
  .map(w => w.toLowerCase())
31
31
  .filter(Boolean)
@@ -85,11 +85,11 @@ function inferPurpose(
85
85
  const subject = words.slice(1).join(' ')
86
86
  base = `Check ${firstWord === 'is' || firstWord === 'has' || firstWord === 'can' ? 'if' : ''} ${subject}`.replace(/ +/g, ' ')
87
87
  } else {
88
- // Genericjust humanise the name
88
+ // Generic just humanise the name
89
89
  base = capitalise(words.join(' '))
90
90
  }
91
91
 
92
- // Append param hint if 3 params and they have meaningful names
92
+ // Append param hint if <=3 params and they have meaningful names
93
93
  if (params && params.length > 0 && params.length <= 3) {
94
94
  const meaningful = params
95
95
  .map(p => p.name)
@@ -107,16 +107,17 @@ function capitalise(s: string): string {
107
107
  }
108
108
 
109
109
  /**
110
- * LockCompiler takes a DependencyGraph and a MikkContract
110
+ * LockCompiler -- takes a DependencyGraph and a MikkContract
111
111
  * and compiles the complete mikk.lock.json.
112
112
  */
113
113
  export class LockCompiler {
114
- /** Main entry compile full lock from graph + contract + parsed files */
114
+ /** Main entry -- compile full lock from graph + contract + parsed files */
115
115
  compile(
116
116
  graph: DependencyGraph,
117
117
  contract: MikkContract,
118
118
  parsedFiles: ParsedFile[],
119
- contextFiles?: ContextFile[]
119
+ contextFiles?: ContextFile[],
120
+ projectRoot?: string
120
121
  ): MikkLock {
121
122
  const functions = this.compileFunctions(graph, contract)
122
123
  const classes = this.compileClasses(graph, contract)
@@ -134,7 +135,7 @@ export class LockCompiler {
134
135
  version: '1.7.0',
135
136
  generatedAt: new Date().toISOString(),
136
137
  generatorVersion: VERSION,
137
- projectRoot: contract.project.name,
138
+ projectRoot: projectRoot ?? contract.project.name,
138
139
  syncState: {
139
140
  status: 'clean',
140
141
  lastSyncAt: new Date().toISOString(),
@@ -244,7 +245,7 @@ export class LockCompiler {
244
245
  const raw: Record<string, any> = {}
245
246
  for (const [id, node] of graph.nodes) {
246
247
  if (node.type !== 'generic') continue
247
- // Only include exported genericsnon-exported types/interfaces are
248
+ // Only include exported generics non-exported types/interfaces are
248
249
  // internal implementation details that add noise without value.
249
250
  if (!node.metadata.isExported) continue
250
251
  const moduleId = this.findModule(node.file, contract.declared.modules)