@getmikk/core 1.7.0 → 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 +82 -412
- package/package.json +3 -1
- package/src/contract/contract-reader.ts +2 -2
- package/src/contract/lock-compiler.ts +15 -14
- package/src/contract/lock-reader.ts +14 -14
- package/src/contract/schema.ts +3 -3
- package/src/index.ts +1 -0
- package/src/parser/base-parser.ts +1 -1
- package/src/parser/boundary-checker.ts +74 -212
- package/src/parser/go/go-extractor.ts +10 -10
- package/src/parser/go/go-parser.ts +2 -2
- package/src/parser/index.ts +45 -31
- package/src/parser/javascript/js-extractor.ts +9 -9
- package/src/parser/javascript/js-parser.ts +2 -2
- package/src/parser/tree-sitter/parser.ts +228 -0
- package/src/parser/tree-sitter/queries.ts +181 -0
- package/src/parser/types.ts +1 -1
- package/src/parser/typescript/ts-extractor.ts +15 -15
- package/src/parser/typescript/ts-parser.ts +1 -1
- package/src/parser/typescript/ts-resolver.ts +2 -2
- package/src/search/bm25.ts +206 -0
- package/src/search/index.ts +3 -0
- package/src/utils/fs.ts +31 -31
- package/src/utils/minimatch.ts +23 -14
- package/test-output.txt +0 -0
- package/tests/go-parser.test.ts +10 -10
- package/tests/js-parser.test.ts +34 -19
- package/tests/parser.test.ts +5 -5
- package/tests/tree-sitter-parser.test.ts +168 -0
- package/tests/ts-parser.test.ts +49 -1
- package/out.log +0 -0
package/README.md
CHANGED
|
@@ -1,469 +1,139 @@
|
|
|
1
|
-
# @getmikk/core
|
|
1
|
+
# @getmikk/core
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> AST parsing, dependency graph, Merkle hashing, contract management, boundary enforcement.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@getmikk/core)
|
|
6
6
|
[](../../LICENSE)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Foundation package for the Mikk ecosystem. All other packages depend on core — nothing in core depends on them.
|
|
9
9
|
|
|
10
|
-
|
|
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
|
-
##
|
|
14
|
+
## What is in core
|
|
17
15
|
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
import { TypeScriptParser, getParser, parseFiles } from '@getmikk/core'
|
|
29
|
+
### GraphBuilder
|
|
62
30
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
const parser = getParser('component.tsx') // returns TypeScriptParser
|
|
37
|
+
### ImpactAnalyzer
|
|
75
38
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
46
|
+
### ClusterDetector
|
|
82
47
|
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
import { TypeScriptResolver } from '@getmikk/core'
|
|
62
|
+
### Merkle Hashing
|
|
97
63
|
|
|
98
|
-
|
|
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
|
-
|
|
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
|
-
|
|
71
|
+
### LockCompiler
|
|
130
72
|
|
|
131
|
-
|
|
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
|
-
|
|
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
|
-
|
|
80
|
+
### ContractReader / ContractWriter / LockReader
|
|
141
81
|
|
|
142
|
-
|
|
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
|
-
|
|
84
|
+
### AdrManager
|
|
156
85
|
|
|
157
|
-
|
|
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
|
-
|
|
162
|
-
Edge types: `import`, `call`, `containment`, `implements`
|
|
88
|
+
### DeadCodeDetector
|
|
163
89
|
|
|
164
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
208
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
##
|
|
133
|
+
## Test Coverage
|
|
447
134
|
|
|
448
|
-
|
|
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
|
-
```
|
|
451
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
13
|
+
// Heuristic purpose inference
|
|
14
14
|
// When JSDoc is missing we derive a short purpose string from:
|
|
15
|
-
// 1. camelCase / PascalCase function name
|
|
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"]
|
|
21
|
-
// "DashboardPage" + returnType:"JSX.Element"
|
|
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
|
|
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
|
-
// Generic
|
|
88
|
+
// Generic just humanise the name
|
|
89
89
|
base = capitalise(words.join(' '))
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
// Append param hint if
|
|
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
|
|
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
|
|
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 generics
|
|
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)
|