@getmikk/intent-engine 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,312 +1,125 @@
1
- # @getmikk/intent-engine
1
+ # @getmikk/intent-engine
2
2
 
3
- > Architectural pre-flight check if your idea is safe before writing a single line.
3
+ > Parse developer intent, detect constraint conflicts, and find functions by meaning.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/@getmikk/intent-engine)](https://www.npmjs.com/package/@getmikk/intent-engine)
6
6
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](../../LICENSE)
7
7
 
8
- `@getmikk/intent-engine` is the pre-flight check layer. You describe what you want to build in plain English — *"add a caching layer to the auth module"* — and before any code is written, the engine interprets your intent into structured objects, checks it against every architectural constraint in `mikk.json`, detects conflicts and layer violations, and generates a concrete implementation plan with which files to touch and what to create.
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
- For AI coding agents, this is the guardrail that prevents architecturally unsafe code generation. For human developers, it's the equivalent of running your idea past a senior architect who knows every constraint in the codebase.
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
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
- ## Quick Start
18
+ ### Usage
29
19
 
30
- ```typescript
31
- import { PreflightPipeline } from '@getmikk/intent-engine'
32
- import { ContractReader, LockReader } from '@getmikk/core'
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('refactor the payment module to use Stripe')
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
- const interpreter = new IntentInterpreter(contract, lock)
106
- const intents = await interpreter.interpret('add input validation to the signup form')
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
- **How it works:**
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
- import { ConflictDetector } from '@getmikk/intent-engine'
139
-
140
- const detector = new ConflictDetector(contract, lock)
141
- const result = detector.detect(intents)
142
-
143
- if (result.hasConflicts) {
144
- for (const conflict of result.conflicts) {
145
- console.warn(`[${conflict.severity}] ${conflict.message}`)
146
- if (conflict.suggestedFix) {
147
- console.log(` Fix: ${conflict.suggestedFix}`)
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
- **Constraint types checked:**
73
+ ### Constraint checks
154
74
 
155
- | Constraint | Description | Example |
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
- ### Suggester
79
+ ## Semantic Search
184
80
 
185
- Generates concrete implementation suggestions based on intents and the current codebase state.
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
- const suggester = new Suggester(contract, lock)
191
- const suggestions = suggester.suggest(intents)
83
+ ### Setup
192
84
 
193
- for (const s of suggestions) {
194
- console.log(`Action: ${s.intent.action} ${s.intent.target.name}`)
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
- **`Suggestion`:**
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
- Finds functions semantically similar to a natural-language query using local embeddings via [`@xenova/transformers`](https://github.com/xenova/transformers.js). No API key required — the model runs entirely offline.
91
+ ### Usage
216
92
 
217
- **Model:** `Xenova/all-MiniLM-L6-v2` (~22 MB, downloaded once to `~/.cache/huggingface` on first use)
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
- // Check if @xenova/transformers is installed before using
228
- if (await SemanticSearcher.isAvailable()) {
229
- const searcher = new SemanticSearcher(projectRoot)
98
+ const searcher = new SemanticSearcher(projectRoot)
230
99
 
231
- // index() builds embeddings; subsequent calls are O(1) cache hits
232
- await searcher.index(lock)
100
+ // Build (or load from cache) embeddings for the lock
101
+ await searcher.index(lock)
233
102
 
234
- // search() returns the topK most relevant functions
235
- const results = await searcher.search('validate JWT and return user payload', lock, 5)
236
- for (const r of results) {
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
- **Cache behaviour:** Embeddings are persisted to `{projectRoot}/.mikk/embeddings.json` and fingerprinted by function count + first 20 sorted IDs. Re-indexing only re-embeds when the lock actually changes (e.g. after `mikk sync`). A cache hit costs a single disk read.
108
+ ### How it works
244
109
 
245
- **`SemanticMatch`:**
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
- | Field | Type | Description |
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
- **API:**
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
- import type {
299
- Intent,
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.7.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.7.1",
24
+ "@getmikk/core": "^1.8.0",
25
25
  "zod": "^3.22.0"
26
26
  },
27
27
  "peerDependencies": {
@@ -106,7 +106,7 @@ export class ConflictDetector {
106
106
  }
107
107
  }
108
108
 
109
- // ── Constraint Classification & Checking ─────────────────────
109
+ // --- Constraint Classification & Checking ---------------------
110
110
 
111
111
  private classifyConstraint(text: string): ConstraintType {
112
112
  const lower = text.toLowerCase()
@@ -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 unit-normed vector
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 finds functions semantically similar to a natural-language
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 cache hit is O(1) disk read.
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
- // ── Cache hit ──────────────────────────────────────────────────────
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 rebuild */ }
80
+ } catch { /* miss or corrupt -- rebuild */ }
81
81
 
82
- // ── Empty lock fast-path nothing to embed ────────────────────────
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
- // ─── Helpers ─────────────────────────────────────────────────────────────────
157
+ // --- Helpers -----------------------------------------------------------------
158
158
 
159
159
  /** Lightweight fingerprint: function count + first 20 sorted IDs */
160
160
  function lockFingerprint(lock: MikkLock): string {