@getmikk/intent-engine 2.0.14 → 2.0.16
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getmikk/intent-engine",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.16",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"lint": "bunx eslint --config ../../eslint.config.mjs ."
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@getmikk/core": "^2.0.
|
|
30
|
+
"@getmikk/core": "^2.0.16",
|
|
31
|
+
"@xenova/transformers": "^2.17.2",
|
|
31
32
|
"zod": "^3.22.0"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
package/src/auto-correction.ts
CHANGED
|
@@ -60,8 +60,8 @@ export class AutoCorrectionEngine {
|
|
|
60
60
|
} else {
|
|
61
61
|
failedFixes.push(`${file}:${issue.line} — ${issue.message}`)
|
|
62
62
|
}
|
|
63
|
-
} catch {
|
|
64
|
-
failedFixes.push(`${file}:${issue.line} — ${issue.message}`)
|
|
63
|
+
} catch (err) {
|
|
64
|
+
failedFixes.push(`${file}:${issue.line} — ${issue.message}: ${err instanceof Error ? err.message : String(err)}`)
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
}
|
package/src/conflict-detector.ts
CHANGED
|
@@ -149,7 +149,7 @@ export class ConflictDetector {
|
|
|
149
149
|
return null
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
const [,
|
|
152
|
+
const [, , allowedPath] = match
|
|
153
153
|
const allowed = allowedPath.trim().replace(/[/\\*]*/g, '')
|
|
154
154
|
const targetModule = intent.target.moduleId || ''
|
|
155
155
|
|
package/src/interpreter.ts
CHANGED
|
@@ -209,7 +209,7 @@ export class PreEditValidation {
|
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
/** Build a zero-impact result when no tracked functions exist. */
|
|
212
|
-
private emptyImpact(
|
|
212
|
+
private emptyImpact(_files: string[]): ImpactResult {
|
|
213
213
|
return {
|
|
214
214
|
changed: [],
|
|
215
215
|
impacted: [],
|
|
@@ -251,6 +251,7 @@ export class PreEditValidation {
|
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
254
255
|
private buildRecommendations(intent: any, impact: ImpactResult, gates: any[], corrections: any): string[] {
|
|
255
256
|
const recs: string[] = []
|
|
256
257
|
|
|
@@ -268,6 +269,7 @@ export class PreEditValidation {
|
|
|
268
269
|
|
|
269
270
|
const warnings = gates.filter(g => g.severity === 'WARNING' && !g.canProceed)
|
|
270
271
|
if (warnings.length > 0) {
|
|
272
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
271
273
|
recs.push(`Address warnings: ${warnings.map((w: any) => w.gate).join(', ')}`)
|
|
272
274
|
}
|
|
273
275
|
if (corrections.issues.length > 0) {
|
package/src/semantic-searcher.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as path from 'node:path'
|
|
2
2
|
import * as fs from 'node:fs/promises'
|
|
3
|
-
import type { MikkLock } from '@getmikk/core'
|
|
3
|
+
import type { MikkLock, MikkLockFunction } from '@getmikk/core'
|
|
4
|
+
|
|
5
|
+
const MAX_BODY_TOKENS = 150
|
|
4
6
|
|
|
5
7
|
interface EmbeddingCache {
|
|
6
8
|
lockFingerprint: string
|
|
@@ -35,6 +37,7 @@ export class SemanticSearcher {
|
|
|
35
37
|
static readonly MODEL = 'Xenova/all-MiniLM-L6-v2'
|
|
36
38
|
|
|
37
39
|
private readonly cachePath: string
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
41
|
private pipeline: any = null
|
|
39
42
|
private cache: EmbeddingCache | null = null
|
|
40
43
|
|
|
@@ -77,7 +80,9 @@ export class SemanticSearcher {
|
|
|
77
80
|
this.cache = cached
|
|
78
81
|
return
|
|
79
82
|
}
|
|
80
|
-
} catch {
|
|
83
|
+
} catch (err) {
|
|
84
|
+
console.warn(`[mikk] Semantic search cache miss/rebuild: ${err instanceof Error ? err.message : String(err)}`)
|
|
85
|
+
}
|
|
81
86
|
|
|
82
87
|
// -- Empty lock fast-path -- nothing to embed ------------------------
|
|
83
88
|
const fns = Object.values(lock.functions)
|
|
@@ -86,16 +91,8 @@ export class SemanticSearcher {
|
|
|
86
91
|
return
|
|
87
92
|
}
|
|
88
93
|
|
|
89
|
-
// Text representation: name + purpose +
|
|
90
|
-
const texts = fns.map(fn =>
|
|
91
|
-
const parts: string[] = [fn.name]
|
|
92
|
-
if (fn.purpose) parts.push(fn.purpose)
|
|
93
|
-
if (fn.params?.length) parts.push(fn.params.map((p: any) => p.name).join(' '))
|
|
94
|
-
if (fn.returnType && fn.returnType !== 'void' && fn.returnType !== 'any') {
|
|
95
|
-
parts.push('returns ' + fn.returnType)
|
|
96
|
-
}
|
|
97
|
-
return parts.join(' ')
|
|
98
|
-
})
|
|
94
|
+
// Text representation: name + purpose + params + types + return type + body snippet
|
|
95
|
+
const texts = await Promise.all(fns.map(fn => buildRichText(fn, this.projectRoot)))
|
|
99
96
|
|
|
100
97
|
await this.ensurePipeline()
|
|
101
98
|
const embeddings: Record<string, number[]> = {}
|
|
@@ -109,8 +106,12 @@ export class SemanticSearcher {
|
|
|
109
106
|
}
|
|
110
107
|
|
|
111
108
|
this.cache = { lockFingerprint: fingerprint, model: SemanticSearcher.MODEL, embeddings }
|
|
112
|
-
|
|
113
|
-
|
|
109
|
+
try {
|
|
110
|
+
await fs.mkdir(path.dirname(this.cachePath), { recursive: true })
|
|
111
|
+
await fs.writeFile(this.cachePath, JSON.stringify(this.cache))
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.warn(`[mikk] Failed to write semantic search cache: ${err instanceof Error ? err.message : String(err)}`)
|
|
114
|
+
}
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
/**
|
|
@@ -154,12 +155,97 @@ export class SemanticSearcher {
|
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
|
|
158
|
+
async function buildRichText(fn: MikkLockFunction, projectRoot: string): Promise<string> {
|
|
159
|
+
const parts: string[] = [fn.name]
|
|
160
|
+
|
|
161
|
+
if (fn.purpose) {
|
|
162
|
+
parts.push(fn.purpose)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (fn.params?.length) {
|
|
166
|
+
const paramStr = fn.params.map((p) => p.name).join(' ')
|
|
167
|
+
const typeStr = fn.params.map((p) => p.type || '').filter(Boolean).join(' ')
|
|
168
|
+
parts.push(paramStr, typeStr)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (fn.returnType && fn.returnType !== 'void' && fn.returnType !== 'any') {
|
|
172
|
+
parts.push('returns', fn.returnType)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const body = await getFunctionBodySnippet(fn, projectRoot)
|
|
176
|
+
if (body) {
|
|
177
|
+
parts.push(body)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return parts.join(' ')
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function getFunctionBodySnippet(fn: MikkLockFunction, projectRoot: string): Promise<string> {
|
|
184
|
+
try {
|
|
185
|
+
const fullPath = path.join(projectRoot, fn.file)
|
|
186
|
+
const content = await fs.readFile(fullPath, 'utf-8')
|
|
187
|
+
const lines = content.split('\n')
|
|
188
|
+
const start = Math.max(0, fn.startLine - 1)
|
|
189
|
+
const end = Math.min(lines.length, fn.endLine)
|
|
190
|
+
const bodyLines = lines.slice(start, end)
|
|
191
|
+
const bodyText = bodyLines.join(' ')
|
|
158
192
|
|
|
159
|
-
|
|
193
|
+
const cleaned = cleanCodeForEmbedding(bodyText)
|
|
194
|
+
|
|
195
|
+
const tokens = cleaned.split(/\s+/).filter(Boolean)
|
|
196
|
+
if (tokens.length <= MAX_BODY_TOKENS) {
|
|
197
|
+
return cleaned
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const truncated = tokens.slice(0, MAX_BODY_TOKENS).join(' ')
|
|
201
|
+
return truncated + ' ...'
|
|
202
|
+
} catch {
|
|
203
|
+
return ''
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function cleanCodeForEmbedding(code: string): string {
|
|
208
|
+
return code
|
|
209
|
+
.replace(/\/\*\*[\s\S]*?\*\//g, ' ')
|
|
210
|
+
.replace(/\/\/[^\n]*/g, ' ')
|
|
211
|
+
.replace(/#.*$/gm, ' ')
|
|
212
|
+
.replace(/['"`][^'"`]*['"`]/g, ' ')
|
|
213
|
+
.replace(/\{[^}]*\}/g, ' ')
|
|
214
|
+
.replace(/\s+/g, ' ')
|
|
215
|
+
.trim()
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function _readFileCached(filePath: string, cache: Map<string, string>): Promise<string> {
|
|
219
|
+
if (cache.has(filePath)) {
|
|
220
|
+
return cache.get(filePath)!
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
224
|
+
cache.set(filePath, content)
|
|
225
|
+
return content
|
|
226
|
+
} catch {
|
|
227
|
+
return ''
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Improved fingerprint: function count + all sorted IDs + metadata */
|
|
160
232
|
function lockFingerprint(lock: MikkLock): string {
|
|
161
|
-
const ids = Object.keys(lock.functions).sort().
|
|
162
|
-
|
|
233
|
+
const ids = Object.keys(lock.functions).sort().join('|')
|
|
234
|
+
const fnCount = Object.keys(lock.functions).length
|
|
235
|
+
const fileCount = Object.keys(lock.files ?? {}).length
|
|
236
|
+
const moduleCount = Object.keys(lock.modules ?? {}).length
|
|
237
|
+
const hash = hashContent(`${fnCount}:${fileCount}:${moduleCount}:${ids}`)
|
|
238
|
+
return hash.slice(0, 32)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function hashContent(content: string): string {
|
|
242
|
+
let hash = 0
|
|
243
|
+
for (let i = 0; i < content.length; i++) {
|
|
244
|
+
const char = content.charCodeAt(i)
|
|
245
|
+
hash = ((hash << 5) - hash) + char
|
|
246
|
+
hash = hash & hash
|
|
247
|
+
}
|
|
248
|
+
return Math.abs(hash).toString(16)
|
|
163
249
|
}
|
|
164
250
|
|
|
165
251
|
function cosineSimilarity(a: number[], b: number[]): number {
|
|
@@ -119,7 +119,7 @@ describe('ConflictDetector', () => {
|
|
|
119
119
|
confidence: 0.8,
|
|
120
120
|
}])
|
|
121
121
|
// The "No direct DB access outside db/" constraint should fire
|
|
122
|
-
|
|
122
|
+
result.conflicts.find(c =>
|
|
123
123
|
c.message.toLowerCase().includes('db') || c.message.toLowerCase().includes('restricted')
|
|
124
124
|
)
|
|
125
125
|
// This may or may not fire depending on exact matching — test the shape
|