@getmikk/intent-engine 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 +71 -258
- package/package.json +2 -2
- package/src/conflict-detector.ts +1 -1
- package/src/semantic-searcher.ts +7 -7
package/README.md
CHANGED
|
@@ -1,312 +1,125 @@
|
|
|
1
|
-
# @getmikk/intent-engine
|
|
1
|
+
# @getmikk/intent-engine
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Parse developer intent, detect constraint conflicts, and find functions by meaning.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@getmikk/intent-engine)
|
|
6
6
|
[](../../LICENSE)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Two capabilities in one package: a pre-flight pipeline that catches architectural conflicts before code is written, and a semantic search engine that finds functions by natural-language description using local vector embeddings.
|
|
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
|
-
##
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
npm install @getmikk/intent-engine
|
|
20
|
-
# or
|
|
21
|
-
bun add @getmikk/intent-engine
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
**Peer dependency:** `@getmikk/core`
|
|
14
|
+
## Pre-flight Pipeline
|
|
25
15
|
|
|
26
|
-
|
|
16
|
+
Takes a plain-English prompt describing a refactor or new feature, and returns a structured verdict before any code is written.
|
|
27
17
|
|
|
28
|
-
|
|
18
|
+
### Usage
|
|
29
19
|
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const contract = await new ContractReader().read('./mikk.json')
|
|
35
|
-
const lock = await new LockReader().read('./mikk.lock.json')
|
|
36
|
-
|
|
37
|
-
const pipeline = new PreflightPipeline(contract, lock)
|
|
38
|
-
const result = await pipeline.run('Add a Redis caching layer to the auth module')
|
|
39
|
-
|
|
40
|
-
console.log(result.intents) // Parsed intent objects
|
|
41
|
-
console.log(result.conflicts) // Constraint violations found
|
|
42
|
-
console.log(result.suggestions) // File-level implementation plan
|
|
43
|
-
console.log(result.approved) // true if no blocking conflicts
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
## Pipeline Architecture
|
|
49
|
-
|
|
50
|
-
```
|
|
51
|
-
Natural Language Prompt
|
|
52
|
-
│
|
|
53
|
-
▼
|
|
54
|
-
┌──────────────────┐
|
|
55
|
-
│ IntentInterpreter │ → Intent[]
|
|
56
|
-
└────────┬─────────┘
|
|
57
|
-
│
|
|
58
|
-
▼
|
|
59
|
-
┌──────────────────┐
|
|
60
|
-
│ ConflictDetector │ → ConflictResult
|
|
61
|
-
└────────┬─────────┘
|
|
62
|
-
│
|
|
63
|
-
▼
|
|
64
|
-
┌──────────────────┐
|
|
65
|
-
│ Suggester │ → Suggestion[]
|
|
66
|
-
└────────┬─────────┘
|
|
67
|
-
│
|
|
68
|
-
▼
|
|
69
|
-
PreflightResult
|
|
20
|
+
```bash
|
|
21
|
+
mikk intent "Move user validation into a shared utils module"
|
|
22
|
+
mikk intent "Extract auth logic into middleware" --json
|
|
70
23
|
```
|
|
71
24
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
## API Reference
|
|
75
|
-
|
|
76
|
-
### PreflightPipeline
|
|
77
|
-
|
|
78
|
-
The main entry point — orchestrates the full interpret → detect → suggest flow.
|
|
25
|
+
Or programmatically:
|
|
79
26
|
|
|
80
27
|
```typescript
|
|
81
28
|
import { PreflightPipeline } from '@getmikk/intent-engine'
|
|
82
29
|
|
|
83
30
|
const pipeline = new PreflightPipeline(contract, lock)
|
|
84
|
-
const result = await pipeline.run(
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
**`PreflightResult`:**
|
|
88
|
-
|
|
89
|
-
| Field | Type | Description |
|
|
90
|
-
|-------|------|-------------|
|
|
91
|
-
| `intents` | `Intent[]` | Structured interpretation of the prompt |
|
|
92
|
-
| `conflicts` | `ConflictResult` | Any constraint violations detected |
|
|
93
|
-
| `suggestions` | `Suggestion[]` | Concrete implementation suggestions |
|
|
94
|
-
| `approved` | `boolean` | `true` if no error-level conflicts |
|
|
95
|
-
|
|
96
|
-
---
|
|
97
|
-
|
|
98
|
-
### IntentInterpreter
|
|
99
|
-
|
|
100
|
-
Parses natural-language prompts into structured intent objects using heuristic keyword matching and fuzzy matching against the lock file's function/module inventory.
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
import { IntentInterpreter } from '@getmikk/intent-engine'
|
|
31
|
+
const result = await pipeline.run("Add rate limiting to all API routes")
|
|
104
32
|
|
|
105
|
-
|
|
106
|
-
|
|
33
|
+
console.log(result.approved) // true | false
|
|
34
|
+
console.log(result.conflicts) // constraint violations found
|
|
35
|
+
console.log(result.suggestions) // implementation suggestions with affected files
|
|
107
36
|
```
|
|
108
37
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
1. **Action verb detection** — Scans for keywords like `create`, `add`, `modify`, `update`, `delete`, `remove`, `refactor`, `move`, `rename`
|
|
112
|
-
2. **Target resolution** — Matches mentioned names against lock file functions, classes, modules, and files using fuzzy matching
|
|
113
|
-
3. **Confidence scoring** — Higher confidence for exact matches, lower for fuzzy
|
|
114
|
-
|
|
115
|
-
**`Intent`:**
|
|
116
|
-
|
|
117
|
-
```typescript
|
|
118
|
-
type Intent = {
|
|
119
|
-
action: 'create' | 'modify' | 'delete' | 'refactor' | 'move'
|
|
120
|
-
target: {
|
|
121
|
-
type: 'function' | 'class' | 'module' | 'file'
|
|
122
|
-
name: string
|
|
123
|
-
moduleId?: string // Which module contains the target
|
|
124
|
-
filePath?: string // Resolved file path
|
|
125
|
-
}
|
|
126
|
-
reason: string // Why this intent was derived
|
|
127
|
-
confidence: number // 0-1 confidence score
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
### ConflictDetector
|
|
134
|
-
|
|
135
|
-
Rule-based constraint checker that validates intents against the architectural rules in `mikk.json`.
|
|
38
|
+
### What it returns
|
|
136
39
|
|
|
137
40
|
```typescript
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
41
|
+
{
|
|
42
|
+
intents: [
|
|
43
|
+
{
|
|
44
|
+
action: 'add' | 'move' | 'extract' | 'refactor' | 'remove' | ...,
|
|
45
|
+
target: { type: 'function' | 'module' | 'file', name: string, moduleId?: string },
|
|
46
|
+
confidence: number // 0-1
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
conflicts: {
|
|
50
|
+
hasConflicts: boolean,
|
|
51
|
+
conflicts: [
|
|
52
|
+
{
|
|
53
|
+
type: string,
|
|
54
|
+
severity: 'error' | 'warning',
|
|
55
|
+
message: string,
|
|
56
|
+
suggestedFix: string
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
suggestions: [
|
|
61
|
+
{
|
|
62
|
+
intent: Intent,
|
|
63
|
+
implementation: string,
|
|
64
|
+
affectedFiles: string[],
|
|
65
|
+
newFiles: string[],
|
|
66
|
+
estimatedImpact: number
|
|
148
67
|
}
|
|
149
|
-
|
|
68
|
+
],
|
|
69
|
+
approved: boolean
|
|
150
70
|
}
|
|
151
71
|
```
|
|
152
72
|
|
|
153
|
-
|
|
73
|
+
### Constraint checks
|
|
154
74
|
|
|
155
|
-
|
|
156
|
-
|-----------|-------------|---------|
|
|
157
|
-
| `no-import` | Module A must not import from Module B | `"no-import": ["payments"]` in the auth module |
|
|
158
|
-
| `must-use` | Module must use specified dependencies | `"must-use": ["@getmikk/core"]` |
|
|
159
|
-
| `no-call` | Functions in module must not call specified targets | `"no-call": ["database.rawQuery"]` |
|
|
160
|
-
| `layer` | Enforces layered architecture ordering | `"layer": 2` — can only import from lower layers |
|
|
161
|
-
| `naming` | Enforces naming patterns for functions/files | `"naming": { "functions": "^handle|^use|^get" }` |
|
|
162
|
-
| `max-files` | Limits the number of files in a module | `"max-files": 20` |
|
|
163
|
-
|
|
164
|
-
**Additional checks:**
|
|
165
|
-
- **Boundary crossing** — Detects when an intent would create a new cross-module dependency
|
|
166
|
-
- **Missing dependencies** — Flags when a target module doesn't exist
|
|
167
|
-
- **Ownership warnings** — Warns when modifying code owned by a different team/module
|
|
168
|
-
|
|
169
|
-
**`Conflict`:**
|
|
170
|
-
|
|
171
|
-
```typescript
|
|
172
|
-
type Conflict = {
|
|
173
|
-
type: 'constraint-violation' | 'ownership-conflict' | 'boundary-crossing' | 'missing-dependency'
|
|
174
|
-
severity: 'error' | 'warning'
|
|
175
|
-
message: string
|
|
176
|
-
relatedIntent: Intent
|
|
177
|
-
suggestedFix?: string
|
|
178
|
-
}
|
|
179
|
-
```
|
|
75
|
+
The pipeline checks against all 6 declared constraint types: `no-import`, `must-use`, `no-call`, `layer`, `naming`, `max-files`. If the proposed change would violate any of them, it surfaces as a conflict with a suggested fix.
|
|
180
76
|
|
|
181
77
|
---
|
|
182
78
|
|
|
183
|
-
|
|
79
|
+
## Semantic Search
|
|
184
80
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
```typescript
|
|
188
|
-
import { Suggester } from '@getmikk/intent-engine'
|
|
81
|
+
Find functions by natural-language description using local vector embeddings. No external API — runs entirely on-device.
|
|
189
82
|
|
|
190
|
-
|
|
191
|
-
const suggestions = suggester.suggest(intents)
|
|
83
|
+
### Setup
|
|
192
84
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
console.log(`Affected files: ${s.affectedFiles.join(', ')}`)
|
|
196
|
-
console.log(`New files: ${s.newFiles.join(', ')}`)
|
|
197
|
-
console.log(`Impact: ${s.estimatedImpact}`)
|
|
198
|
-
}
|
|
85
|
+
```bash
|
|
86
|
+
npm install @xenova/transformers
|
|
199
87
|
```
|
|
200
88
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
| Field | Type | Description |
|
|
204
|
-
|-------|------|-------------|
|
|
205
|
-
| `intent` | `Intent` | The original intent this suggestion addresses |
|
|
206
|
-
| `affectedFiles` | `string[]` | Existing files that would need changes |
|
|
207
|
-
| `newFiles` | `string[]` | Files that would need to be created |
|
|
208
|
-
| `estimatedImpact` | `'low' \| 'medium' \| 'high'` | Blast radius estimate |
|
|
209
|
-
| `implementation` | `string` | Natural-language implementation guidance |
|
|
210
|
-
|
|
211
|
-
---
|
|
212
|
-
|
|
213
|
-
### SemanticSearcher
|
|
89
|
+
The model (`Xenova/all-MiniLM-L6-v2`, ~22MB) downloads once to `~/.cache/huggingface`.
|
|
214
90
|
|
|
215
|
-
|
|
91
|
+
### Usage
|
|
216
92
|
|
|
217
|
-
|
|
218
|
-
**Optional peer dependency:** `@xenova/transformers >= 2`
|
|
219
|
-
|
|
220
|
-
```bash
|
|
221
|
-
bun add @xenova/transformers # only needed if you use SemanticSearcher
|
|
222
|
-
```
|
|
93
|
+
Exposed via the MCP tool `mikk_semantic_search`, or directly:
|
|
223
94
|
|
|
224
95
|
```typescript
|
|
225
96
|
import { SemanticSearcher } from '@getmikk/intent-engine'
|
|
226
97
|
|
|
227
|
-
|
|
228
|
-
if (await SemanticSearcher.isAvailable()) {
|
|
229
|
-
const searcher = new SemanticSearcher(projectRoot)
|
|
98
|
+
const searcher = new SemanticSearcher(projectRoot)
|
|
230
99
|
|
|
231
|
-
|
|
232
|
-
|
|
100
|
+
// Build (or load from cache) embeddings for the lock
|
|
101
|
+
await searcher.index(lock)
|
|
233
102
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
console.log(`${r.name} (${r.file}:${r.lines}) — score: ${r.score}`)
|
|
238
|
-
console.log(` ${r.purpose}`)
|
|
239
|
-
}
|
|
240
|
-
}
|
|
103
|
+
// Find the 10 most semantically similar functions
|
|
104
|
+
const results = await searcher.search('validate a JWT token', lock, 10)
|
|
105
|
+
// Returns: [{ name, file, moduleId, purpose, lines, score }]
|
|
241
106
|
```
|
|
242
107
|
|
|
243
|
-
|
|
108
|
+
### How it works
|
|
244
109
|
|
|
245
|
-
|
|
110
|
+
1. For each function in the lock, concatenates: function name + purpose string (if present) + param names + return type
|
|
111
|
+
2. Generates embeddings in batches of 64 using the pipeline
|
|
112
|
+
3. Caches to `.mikk/embeddings.json` — fingerprinted by function count + first 20 sorted IDs
|
|
113
|
+
4. Cache is valid until the lock changes; recomputes only what changed
|
|
114
|
+
5. At search time, embeds the query and ranks all functions by cosine similarity
|
|
246
115
|
|
|
247
|
-
|
|
248
|
-
|-------|------|-------------|
|
|
249
|
-
| `id` | `string` | Function ID (`fn:module:name`) |
|
|
250
|
-
| `name` | `string` | Function name |
|
|
251
|
-
| `file` | `string` | Source file path |
|
|
252
|
-
| `moduleId` | `string` | Owning module |
|
|
253
|
-
| `purpose` | `string` | One-line purpose from the lock |
|
|
254
|
-
| `lines` | `string` | Line range, e.g. `"12-34"` |
|
|
255
|
-
| `score` | `number` | Cosine similarity `[0, 1]` — higher is more relevant |
|
|
116
|
+
All vectors are unit-normalized at generation time so similarity is a simple dot product.
|
|
256
117
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
| Method | Description |
|
|
260
|
-
|--------|-------------|
|
|
261
|
-
| `SemanticSearcher.isAvailable()` | Returns `true` if `@xenova/transformers` is importable |
|
|
262
|
-
| `new SemanticSearcher(projectRoot)` | Creates an instance scoped to a project root |
|
|
263
|
-
| `.index(lock)` | Builds/loads embeddings for all functions in the lock |
|
|
264
|
-
| `.search(query, lock, topK?)` | Returns top `topK` (default 10) semantically similar functions |
|
|
265
|
-
|
|
266
|
-
> **Note:** Call `index()` before `search()`, otherwise `search()` throws `"Call index() before search()"`. The MCP server keeps a per-project singleton to avoid repeated model loads.
|
|
267
|
-
|
|
268
|
-
---
|
|
269
|
-
|
|
270
|
-
## Usage with AI Agents
|
|
271
|
-
|
|
272
|
-
The intent engine is designed to be called by AI coding agents as a pre-flight check:
|
|
273
|
-
|
|
274
|
-
```typescript
|
|
275
|
-
// In your AI agent's planning phase:
|
|
276
|
-
const pipeline = new PreflightPipeline(contract, lock)
|
|
277
|
-
const preflight = await pipeline.run(userPrompt)
|
|
278
|
-
|
|
279
|
-
if (!preflight.approved) {
|
|
280
|
-
// Show conflicts to user, ask for confirmation
|
|
281
|
-
const errors = preflight.conflicts.conflicts.filter(c => c.severity === 'error')
|
|
282
|
-
throw new Error(`Blocked: ${errors.map(e => e.message).join('; ')}`)
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Use suggestions to guide implementation
|
|
286
|
-
for (const suggestion of preflight.suggestions) {
|
|
287
|
-
// suggestion.affectedFiles — files to read/modify
|
|
288
|
-
// suggestion.newFiles — files to create
|
|
289
|
-
// suggestion.implementation — guidance text
|
|
290
|
-
}
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
---
|
|
294
|
-
|
|
295
|
-
## Types
|
|
118
|
+
### Check availability
|
|
296
119
|
|
|
297
120
|
```typescript
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
Conflict,
|
|
301
|
-
ConflictResult,
|
|
302
|
-
Suggestion,
|
|
303
|
-
PreflightResult,
|
|
304
|
-
AIProviderConfig,
|
|
305
|
-
} from '@getmikk/intent-engine'
|
|
121
|
+
const available = await SemanticSearcher.isAvailable()
|
|
122
|
+
// true if @xenova/transformers is installed and importable
|
|
306
123
|
```
|
|
307
124
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
## License
|
|
311
|
-
|
|
312
|
-
[Apache-2.0](../../LICENSE)
|
|
125
|
+
The MCP server calls `isAvailable()` before registering the tool — if the package is missing, the tool is not exposed and the response explains how to install it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getmikk/intent-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"dev": "tsc --watch"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@getmikk/core": "^1.
|
|
24
|
+
"@getmikk/core": "^1.8.0",
|
|
25
25
|
"zod": "^3.22.0"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
package/src/conflict-detector.ts
CHANGED
|
@@ -106,7 +106,7 @@ export class ConflictDetector {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
//
|
|
109
|
+
// --- Constraint Classification & Checking ---------------------
|
|
110
110
|
|
|
111
111
|
private classifyConstraint(text: string): ConstraintType {
|
|
112
112
|
const lower = text.toLowerCase()
|
package/src/semantic-searcher.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { MikkLock } from '@getmikk/core'
|
|
|
5
5
|
interface EmbeddingCache {
|
|
6
6
|
lockFingerprint: string
|
|
7
7
|
model: string
|
|
8
|
-
embeddings: Record<string, number[]> // fnId
|
|
8
|
+
embeddings: Record<string, number[]> // fnId -> unit-normed vector
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export interface SemanticMatch {
|
|
@@ -19,7 +19,7 @@ export interface SemanticMatch {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* SemanticSearcher
|
|
22
|
+
* SemanticSearcher -- finds functions semantically similar to a natural-language
|
|
23
23
|
* query using local embeddings via @xenova/transformers.
|
|
24
24
|
*
|
|
25
25
|
* Model: Xenova/all-MiniLM-L6-v2 (~22 MB, downloads once to ~/.cache/huggingface).
|
|
@@ -57,12 +57,12 @@ export class SemanticSearcher {
|
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* Build (or load from cache) embeddings for every function in the lock.
|
|
60
|
-
* Safe to call on every MCP request
|
|
60
|
+
* Safe to call on every MCP request -- cache hit is O(1) disk read.
|
|
61
61
|
*/
|
|
62
62
|
async index(lock: MikkLock): Promise<void> {
|
|
63
63
|
const fingerprint = lockFingerprint(lock)
|
|
64
64
|
|
|
65
|
-
//
|
|
65
|
+
// -- Cache hit --------------------------------------------------------
|
|
66
66
|
try {
|
|
67
67
|
const raw = await fs.readFile(this.cachePath, 'utf-8')
|
|
68
68
|
const cached: EmbeddingCache = JSON.parse(raw)
|
|
@@ -77,9 +77,9 @@ export class SemanticSearcher {
|
|
|
77
77
|
this.cache = cached
|
|
78
78
|
return
|
|
79
79
|
}
|
|
80
|
-
} catch { /* miss or corrupt
|
|
80
|
+
} catch { /* miss or corrupt -- rebuild */ }
|
|
81
81
|
|
|
82
|
-
//
|
|
82
|
+
// -- Empty lock fast-path -- nothing to embed ------------------------
|
|
83
83
|
const fns = Object.values(lock.functions)
|
|
84
84
|
if (fns.length === 0) {
|
|
85
85
|
this.cache = { lockFingerprint: fingerprint, model: SemanticSearcher.MODEL, embeddings: {} }
|
|
@@ -154,7 +154,7 @@ export class SemanticSearcher {
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
//
|
|
157
|
+
// --- Helpers -----------------------------------------------------------------
|
|
158
158
|
|
|
159
159
|
/** Lightweight fingerprint: function count + first 20 sorted IDs */
|
|
160
160
|
function lockFingerprint(lock: MikkLock): string {
|