@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.
- package/README.md +431 -0
- package/package.json +6 -2
- package/src/contract/contract-generator.ts +85 -85
- package/src/contract/contract-reader.ts +28 -28
- package/src/contract/contract-writer.ts +114 -114
- package/src/contract/index.ts +12 -12
- package/src/contract/lock-compiler.ts +221 -221
- package/src/contract/lock-reader.ts +34 -34
- package/src/contract/schema.ts +147 -147
- package/src/graph/cluster-detector.ts +312 -312
- package/src/graph/graph-builder.ts +211 -211
- package/src/graph/impact-analyzer.ts +55 -55
- package/src/graph/index.ts +4 -4
- package/src/graph/types.ts +59 -59
- package/src/hash/file-hasher.ts +30 -30
- package/src/hash/hash-store.ts +119 -119
- package/src/hash/index.ts +3 -3
- package/src/hash/tree-hasher.ts +20 -20
- package/src/index.ts +12 -12
- package/src/parser/base-parser.ts +16 -16
- package/src/parser/boundary-checker.ts +211 -211
- package/src/parser/index.ts +46 -46
- package/src/parser/types.ts +90 -90
- package/src/parser/typescript/ts-extractor.ts +543 -543
- package/src/parser/typescript/ts-parser.ts +41 -41
- package/src/parser/typescript/ts-resolver.ts +86 -86
- package/src/utils/errors.ts +42 -42
- package/src/utils/fs.ts +75 -75
- package/src/utils/fuzzy-match.ts +186 -186
- package/src/utils/logger.ts +36 -36
- package/src/utils/minimatch.ts +19 -19
- package/tests/contract.test.ts +134 -134
- package/tests/fixtures/simple-api/package.json +5 -5
- package/tests/fixtures/simple-api/src/auth/middleware.ts +9 -9
- package/tests/fixtures/simple-api/src/auth/verify.ts +6 -6
- package/tests/fixtures/simple-api/src/index.ts +9 -9
- package/tests/fixtures/simple-api/src/utils/jwt.ts +3 -3
- package/tests/fixtures/simple-api/tsconfig.json +8 -8
- package/tests/fuzzy-match.test.ts +142 -142
- package/tests/graph.test.ts +169 -169
- package/tests/hash.test.ts +49 -49
- package/tests/helpers.ts +83 -83
- package/tests/parser.test.ts +218 -218
- package/tsconfig.json +15 -15
package/README.md
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
# @getmikk/core
|
|
2
|
+
|
|
3
|
+
> AST parsing, dependency graph construction, Merkle-tree hashing, contract management, and foundational utilities for the Mikk ecosystem.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@getmikk/core)
|
|
6
|
+
[](../../LICENSE)
|
|
7
|
+
|
|
8
|
+
`@getmikk/core` is the foundation package that every other Mikk package depends on. It provides the complete pipeline for understanding a TypeScript codebase: parsing source files into structured ASTs, building a full dependency graph, computing Merkle-tree hashes for drift detection, and managing the `mikk.json` contract and `mikk.lock.json` lock file.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @getmikk/core
|
|
16
|
+
# or
|
|
17
|
+
bun add @getmikk/core
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Architecture Overview
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
Source Files (.ts/.tsx)
|
|
26
|
+
│
|
|
27
|
+
▼
|
|
28
|
+
┌─────────┐
|
|
29
|
+
│ Parser │ ← TypeScriptParser + TypeScriptExtractor
|
|
30
|
+
└────┬────┘
|
|
31
|
+
│ ParsedFile[]
|
|
32
|
+
▼
|
|
33
|
+
┌──────────────┐
|
|
34
|
+
│ GraphBuilder │ ← Two-pass: nodes → edges
|
|
35
|
+
└──────┬───────┘
|
|
36
|
+
│ DependencyGraph
|
|
37
|
+
▼
|
|
38
|
+
┌────────────────┐
|
|
39
|
+
│ LockCompiler │ ← Merkle-tree hashes
|
|
40
|
+
└───────┬────────┘
|
|
41
|
+
│ MikkLock
|
|
42
|
+
▼
|
|
43
|
+
┌─────────────────┐
|
|
44
|
+
│ ContractWriter │ ← Permission model (never/ask/explicit)
|
|
45
|
+
└─────────────────┘
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Modules
|
|
51
|
+
|
|
52
|
+
### 1. Parser — Source Code Analysis
|
|
53
|
+
|
|
54
|
+
The parser module turns raw TypeScript/TSX files into structured `ParsedFile` objects using the TypeScript Compiler API.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { TypeScriptParser, getParser, parseFiles } from '@getmikk/core'
|
|
58
|
+
|
|
59
|
+
// Parse a single file
|
|
60
|
+
const parser = new TypeScriptParser()
|
|
61
|
+
const parsed = parser.parse('/src/utils/math.ts', fileContent)
|
|
62
|
+
|
|
63
|
+
console.log(parsed.functions) // ParsedFunction[] — name, params, returnType, startLine, endLine, calls[]
|
|
64
|
+
console.log(parsed.classes) // ParsedClass[] — name, methods[], properties[], decorators[]
|
|
65
|
+
console.log(parsed.imports) // ParsedImport[] — source, specifiers, isTypeOnly
|
|
66
|
+
console.log(parsed.exports) // ParsedExport[] — name, isDefault, isTypeOnly
|
|
67
|
+
console.log(parsed.generics) // ParsedGeneric[] — interfaces, types, const declarations
|
|
68
|
+
|
|
69
|
+
// Factory — auto-selects parser by file extension
|
|
70
|
+
const parser = getParser('component.tsx') // returns TypeScriptParser
|
|
71
|
+
|
|
72
|
+
// Batch parse with import resolution
|
|
73
|
+
const files = await parseFiles(filePaths, projectRoot, readFileFn)
|
|
74
|
+
// Returns ParsedFile[] with all import paths resolved to absolute paths
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### TypeScriptExtractor
|
|
78
|
+
|
|
79
|
+
The extractor walks the TypeScript AST and pulls out detailed metadata:
|
|
80
|
+
|
|
81
|
+
- **Functions**: name, parameters (with types & defaults), return type, line range, internal calls, `async`/generator flags, decorators, type parameters
|
|
82
|
+
- **Classes**: name, methods (with full function metadata), properties, decorators, `extends`/`implements`, type parameters
|
|
83
|
+
- **Generics**: interfaces, type aliases, const declarations, enums
|
|
84
|
+
- **Imports**: named, default, namespace, type-only imports
|
|
85
|
+
- **Exports**: named, default, re-exports
|
|
86
|
+
|
|
87
|
+
#### TypeScriptResolver
|
|
88
|
+
|
|
89
|
+
Resolves import paths against the actual project filesystem:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { TypeScriptResolver } from '@getmikk/core'
|
|
93
|
+
|
|
94
|
+
const resolver = new TypeScriptResolver()
|
|
95
|
+
// Resolves: relative paths, path aliases (tsconfig paths), index files, extension inference (.ts/.tsx/.js)
|
|
96
|
+
const resolved = resolver.resolve(importDecl, fromFilePath, allProjectFiles)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
### 2. Graph — Dependency Graph Construction
|
|
102
|
+
|
|
103
|
+
The graph module builds a complete dependency graph from parsed files.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { GraphBuilder, ImpactAnalyzer, ClusterDetector } from '@getmikk/core'
|
|
107
|
+
|
|
108
|
+
// Build the graph
|
|
109
|
+
const builder = new GraphBuilder()
|
|
110
|
+
const graph = builder.build(parsedFiles)
|
|
111
|
+
|
|
112
|
+
console.log(graph.nodes) // Map<string, GraphNode> — file, function, class, generic nodes
|
|
113
|
+
console.log(graph.edges) // GraphEdge[] — import, call, containment, implements edges
|
|
114
|
+
console.log(graph.adjacency) // Map<string, string[]> — forward adjacency
|
|
115
|
+
console.log(graph.reverse) // Map<string, string[]> — reverse adjacency
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### GraphBuilder
|
|
119
|
+
|
|
120
|
+
Two-pass construction:
|
|
121
|
+
1. **Pass 1 — Nodes**: Creates nodes for every file, function, class, and generic declaration
|
|
122
|
+
2. **Pass 2 — Edges**: Creates edges for imports, function calls, class containment, and cross-file references
|
|
123
|
+
|
|
124
|
+
Node types: `file`, `function`, `class`, `generic`
|
|
125
|
+
Edge types: `import`, `call`, `containment`, `implements`
|
|
126
|
+
|
|
127
|
+
#### ImpactAnalyzer
|
|
128
|
+
|
|
129
|
+
BFS backward walk to find everything affected by a change:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
const analyzer = new ImpactAnalyzer(graph)
|
|
133
|
+
const impact = analyzer.analyze(['src/utils/math.ts::calculateTotal'])
|
|
134
|
+
|
|
135
|
+
console.log(impact.changed) // string[] — directly changed node IDs
|
|
136
|
+
console.log(impact.impacted) // string[] — transitively affected nodes
|
|
137
|
+
console.log(impact.depth) // number — max propagation depth
|
|
138
|
+
console.log(impact.confidence) // number — 0-1 confidence score
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### ClusterDetector
|
|
142
|
+
|
|
143
|
+
Greedy agglomeration algorithm for automatic module discovery:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
const detector = new ClusterDetector(graph, /* minClusterSize */ 3, /* minCouplingScore */ 0.1)
|
|
147
|
+
const clusters = detector.detect()
|
|
148
|
+
|
|
149
|
+
// Returns ModuleCluster[] with:
|
|
150
|
+
// - id, label (auto-generated from common paths)
|
|
151
|
+
// - nodeIds[] — functions/classes in this cluster
|
|
152
|
+
// - cohesion — internal coupling score (0-1)
|
|
153
|
+
// - coupling — Map<clusterId, score> — external coupling
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The algorithm starts with one cluster per file, then iteratively merges the pair with the highest coupling score until no pair exceeds the threshold.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
### 3. Contract — mikk.json & mikk.lock.json
|
|
161
|
+
|
|
162
|
+
The contract module manages the two core Mikk files using Zod validation.
|
|
163
|
+
|
|
164
|
+
#### Schemas
|
|
165
|
+
|
|
166
|
+
All schemas are exported as Zod objects for runtime validation:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import {
|
|
170
|
+
MikkContractSchema, // mikk.json validation
|
|
171
|
+
MikkLockSchema, // mikk.lock.json validation
|
|
172
|
+
MikkModuleSchema, // Module definition
|
|
173
|
+
MikkDecisionSchema, // Architecture decision record
|
|
174
|
+
} from '@getmikk/core'
|
|
175
|
+
|
|
176
|
+
// Validate a contract
|
|
177
|
+
const result = MikkContractSchema.safeParse(rawJson)
|
|
178
|
+
if (!result.success) console.error(result.error.issues)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
#### mikk.json (Contract)
|
|
182
|
+
|
|
183
|
+
Defines the project's architectural rules:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
type MikkContract = {
|
|
187
|
+
name: string
|
|
188
|
+
version: string
|
|
189
|
+
modules: Record<string, MikkModule> // Module definitions with intent, public API, constraints
|
|
190
|
+
decisions: MikkDecision[] // Architecture Decision Records (ADRs)
|
|
191
|
+
overwrite: {
|
|
192
|
+
permission: 'never' | 'ask' | 'explicit'
|
|
193
|
+
lastOverwrittenBy?: string
|
|
194
|
+
lastOverwrittenAt?: string
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### mikk.lock.json (Lock File)
|
|
200
|
+
|
|
201
|
+
Auto-generated snapshot of the entire codebase:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
type MikkLock = {
|
|
205
|
+
generatorVersion: string
|
|
206
|
+
generatedAt: string
|
|
207
|
+
rootHash: string // Merkle root of entire project
|
|
208
|
+
modules: Record<string, MikkLockModule>
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
type MikkLockModule = {
|
|
212
|
+
hash: string // Merkle hash of all files in module
|
|
213
|
+
files: Record<string, MikkLockFile>
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
type MikkLockFile = {
|
|
217
|
+
hash: string
|
|
218
|
+
functions: Record<string, MikkLockFunction>
|
|
219
|
+
classes: Record<string, MikkLockClass>
|
|
220
|
+
generics: Record<string, MikkLockGeneric>
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
#### ContractReader / LockReader
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { ContractReader, LockReader } from '@getmikk/core'
|
|
228
|
+
|
|
229
|
+
const contractReader = new ContractReader()
|
|
230
|
+
const contract = await contractReader.read('./mikk.json')
|
|
231
|
+
|
|
232
|
+
const lockReader = new LockReader()
|
|
233
|
+
const lock = await lockReader.read('./mikk.lock.json')
|
|
234
|
+
await lockReader.write(updatedLock, './mikk.lock.json')
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### ContractWriter — Permission Model
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import { ContractWriter } from '@getmikk/core'
|
|
241
|
+
|
|
242
|
+
const writer = new ContractWriter()
|
|
243
|
+
|
|
244
|
+
// First-time write
|
|
245
|
+
await writer.writeNew(contract, './mikk.json')
|
|
246
|
+
|
|
247
|
+
// Update with permission model
|
|
248
|
+
const result = await writer.update(existingContract, updates, './mikk.json')
|
|
249
|
+
// result.updated — boolean
|
|
250
|
+
// result.requiresConfirmation — true if permission is 'ask'
|
|
251
|
+
// result.proposedChanges — diff object when confirmation needed
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Permission levels:
|
|
255
|
+
- **`never`** — Contract is read-only, updates are rejected
|
|
256
|
+
- **`ask`** — Returns `requiresConfirmation: true` with proposed changes
|
|
257
|
+
- **`explicit`** — Auto-applies updates with audit trail
|
|
258
|
+
|
|
259
|
+
#### LockCompiler
|
|
260
|
+
|
|
261
|
+
Compiles the full lock file from graph + contract + parsed files:
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { LockCompiler } from '@getmikk/core'
|
|
265
|
+
|
|
266
|
+
const compiler = new LockCompiler()
|
|
267
|
+
const lock = compiler.compile(graph, contract, parsedFiles)
|
|
268
|
+
// Computes Merkle-tree hashes at every level:
|
|
269
|
+
// function → file → module → root
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
#### ContractGenerator
|
|
273
|
+
|
|
274
|
+
Auto-generates a `mikk.json` skeleton from detected clusters:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { ContractGenerator } from '@getmikk/core'
|
|
278
|
+
|
|
279
|
+
const generator = new ContractGenerator()
|
|
280
|
+
const contract = generator.generateFromClusters(clusters, parsedFiles, 'my-project')
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
#### BoundaryChecker
|
|
284
|
+
|
|
285
|
+
CI-ready enforcement layer:
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
import { BoundaryChecker } from '@getmikk/core'
|
|
289
|
+
|
|
290
|
+
const checker = new BoundaryChecker(contract, lock)
|
|
291
|
+
const result = checker.check()
|
|
292
|
+
|
|
293
|
+
if (!result.pass) {
|
|
294
|
+
for (const v of result.violations) {
|
|
295
|
+
console.error(`${v.severity}: ${v.message}`)
|
|
296
|
+
// severity: 'error' | 'warning'
|
|
297
|
+
// type: 'boundary-crossing' | 'constraint-violation'
|
|
298
|
+
}
|
|
299
|
+
process.exit(1)
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
### 4. Hash — Merkle-Tree Integrity
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
import { hashContent, hashFile, hashFunctionBody, computeModuleHash, computeRootHash } from '@getmikk/core'
|
|
309
|
+
|
|
310
|
+
// Hash raw content (SHA-256)
|
|
311
|
+
const h1 = hashContent('function foo() {}')
|
|
312
|
+
|
|
313
|
+
// Hash a file from disk
|
|
314
|
+
const h2 = await hashFile('/src/index.ts')
|
|
315
|
+
|
|
316
|
+
// Hash a specific line range (function body)
|
|
317
|
+
const h3 = hashFunctionBody(fileContent, 10, 25)
|
|
318
|
+
|
|
319
|
+
// Merkle tree
|
|
320
|
+
const moduleHash = computeModuleHash(['fileHash1', 'fileHash2'])
|
|
321
|
+
const rootHash = computeRootHash(['moduleHash1', 'moduleHash2'])
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
#### HashStore — SQLite Persistence
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
import { HashStore } from '@getmikk/core'
|
|
328
|
+
|
|
329
|
+
const store = new HashStore('/project/.mikk/hashes.db')
|
|
330
|
+
|
|
331
|
+
store.set('src/index.ts', 'abc123...', 4096)
|
|
332
|
+
const entry = store.get('src/index.ts')
|
|
333
|
+
// { path, hash, size, updatedAt }
|
|
334
|
+
|
|
335
|
+
const changed = store.getChangedSince(Date.now() - 60_000)
|
|
336
|
+
store.delete('src/old-file.ts')
|
|
337
|
+
|
|
338
|
+
// Batch operations use SQLite transactions for performance
|
|
339
|
+
const allPaths = store.getAllPaths()
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Uses SQLite in WAL mode for concurrent read access and fast writes.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
### 5. Utilities
|
|
347
|
+
|
|
348
|
+
#### Error Hierarchy
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
import {
|
|
352
|
+
MikkError, // Base error class
|
|
353
|
+
ParseError, // File parsing failures
|
|
354
|
+
ContractNotFoundError, // mikk.json not found
|
|
355
|
+
LockNotFoundError, // mikk.lock.json not found
|
|
356
|
+
UnsupportedLanguageError, // Unsupported file extension
|
|
357
|
+
OverwritePermissionError, // Contract overwrite denied
|
|
358
|
+
SyncStateError, // Lock file out of sync
|
|
359
|
+
} from '@getmikk/core'
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
#### Logging
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
import { logger, setLogLevel } from '@getmikk/core'
|
|
366
|
+
|
|
367
|
+
setLogLevel('debug') // 'debug' | 'info' | 'warn' | 'error' | 'silent'
|
|
368
|
+
|
|
369
|
+
logger.info('Analysis complete', { files: 42, duration: '1.2s' })
|
|
370
|
+
// Outputs structured JSON to stderr
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
#### File Utilities
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
import { discoverFiles, readFileContent, writeFileContent, fileExists, setupMikkDirectory } from '@getmikk/core'
|
|
377
|
+
|
|
378
|
+
// Discover TypeScript files
|
|
379
|
+
const files = await discoverFiles('/project', ['src/**/*.ts'], ['node_modules'])
|
|
380
|
+
|
|
381
|
+
// Read/write
|
|
382
|
+
const content = await readFileContent('/src/index.ts')
|
|
383
|
+
await writeFileContent('/output/result.json', jsonString) // auto-creates directories
|
|
384
|
+
|
|
385
|
+
// Initialize .mikk/ directory structure
|
|
386
|
+
await setupMikkDirectory('/project')
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
#### Fuzzy Matching
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
import { scoreFunctions, findFuzzyMatches, levenshtein } from '@getmikk/core'
|
|
393
|
+
|
|
394
|
+
// Score lock file functions against a natural-language prompt
|
|
395
|
+
const matches = scoreFunctions('calculate the total price', lock, 10)
|
|
396
|
+
// Returns FuzzyMatch[] sorted by relevance score
|
|
397
|
+
|
|
398
|
+
// "Did you mean?" suggestions
|
|
399
|
+
const suggestions = findFuzzyMatches('calcualteTotal', lock, 5)
|
|
400
|
+
// Returns closest function names by Levenshtein distance
|
|
401
|
+
|
|
402
|
+
// Raw edit distance
|
|
403
|
+
const dist = levenshtein('kitten', 'sitting') // 3
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## Types
|
|
409
|
+
|
|
410
|
+
All types are exported and can be imported directly:
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
import type {
|
|
414
|
+
// Parser
|
|
415
|
+
ParsedFile, ParsedFunction, ParsedClass, ParsedImport, ParsedExport, ParsedParam, ParsedGeneric,
|
|
416
|
+
// Graph
|
|
417
|
+
DependencyGraph, GraphNode, GraphEdge, ImpactResult, NodeType, EdgeType, ModuleCluster,
|
|
418
|
+
// Contract
|
|
419
|
+
MikkContract, MikkLock, MikkModule, MikkDecision, MikkLockFunction, MikkLockModule, MikkLockFile,
|
|
420
|
+
// Boundary
|
|
421
|
+
BoundaryViolation, BoundaryCheckResult, ViolationSeverity,
|
|
422
|
+
// Writer
|
|
423
|
+
UpdateResult,
|
|
424
|
+
} from '@getmikk/core'
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## License
|
|
430
|
+
|
|
431
|
+
[Apache-2.0](../../LICENSE)
|
package/package.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getmikk/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/Ansh-dhanani/mikk"
|
|
8
|
+
},
|
|
4
9
|
"type": "module",
|
|
5
10
|
"main": "./dist/index.js",
|
|
6
11
|
"types": "./dist/index.d.ts",
|
|
@@ -13,7 +18,6 @@
|
|
|
13
18
|
"scripts": {
|
|
14
19
|
"build": "tsc",
|
|
15
20
|
"test": "bun test",
|
|
16
|
-
"publish": "npm publish --access public",
|
|
17
21
|
"dev": "tsc --watch"
|
|
18
22
|
},
|
|
19
23
|
"dependencies": {
|
|
@@ -1,85 +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
|
-
}
|
|
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
|
+
}
|
|
@@ -1,28 +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
|
-
}
|
|
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
|
+
}
|