@getmikk/intent-engine 1.2.0 → 1.3.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 +257 -0
- package/package.json +31 -27
- package/src/conflict-detector.ts +302 -302
- package/src/index.ts +6 -6
- package/src/interpreter.ts +216 -216
- package/src/preflight.ts +44 -44
- package/src/suggester.ts +104 -104
- package/src/types.ts +54 -54
- package/tests/intent-engine.test.ts +210 -210
- package/tests/smoke.test.ts +5 -5
- package/tsconfig.json +15 -15
package/src/interpreter.ts
CHANGED
|
@@ -1,216 +1,216 @@
|
|
|
1
|
-
import type { MikkContract, MikkLock } from '@getmikk/core'
|
|
2
|
-
import { IntentSchema, type Intent } from './types.js'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* IntentInterpreter — parses a natural-language prompt into structured
|
|
6
|
-
* intents using heuristic keyword matching and fuzzy matching against
|
|
7
|
-
* the lock file's function/module names.
|
|
8
|
-
*/
|
|
9
|
-
export class IntentInterpreter {
|
|
10
|
-
constructor(
|
|
11
|
-
private contract: MikkContract,
|
|
12
|
-
private lock: MikkLock
|
|
13
|
-
) { }
|
|
14
|
-
|
|
15
|
-
async interpret(prompt: string): Promise<Intent[]> {
|
|
16
|
-
const intents: Intent[] = []
|
|
17
|
-
const promptLower = prompt.toLowerCase()
|
|
18
|
-
|
|
19
|
-
// Detect action verbs
|
|
20
|
-
const actions: Intent['action'][] = []
|
|
21
|
-
if (promptLower.includes('add') || promptLower.includes('create') ||
|
|
22
|
-
promptLower.includes('new') || promptLower.includes('implement')) {
|
|
23
|
-
actions.push('create')
|
|
24
|
-
}
|
|
25
|
-
if (promptLower.includes('modify') || promptLower.includes('change') ||
|
|
26
|
-
promptLower.includes('update') || promptLower.includes('fix') ||
|
|
27
|
-
promptLower.includes('edit') || promptLower.includes('patch')) {
|
|
28
|
-
actions.push('modify')
|
|
29
|
-
}
|
|
30
|
-
if (promptLower.includes('delete') || promptLower.includes('remove') ||
|
|
31
|
-
promptLower.includes('drop')) {
|
|
32
|
-
actions.push('delete')
|
|
33
|
-
}
|
|
34
|
-
if (promptLower.includes('refactor') || promptLower.includes('restructure') ||
|
|
35
|
-
promptLower.includes('clean up') || promptLower.includes('reorganize')) {
|
|
36
|
-
actions.push('refactor')
|
|
37
|
-
}
|
|
38
|
-
if (promptLower.includes('move') || promptLower.includes('migrate') ||
|
|
39
|
-
promptLower.includes('relocate')) {
|
|
40
|
-
actions.push('move')
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Default to modify if no action is detected
|
|
44
|
-
if (actions.length === 0) {
|
|
45
|
-
actions.push('modify')
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Find the best matching target — try functions first, then modules
|
|
49
|
-
const matchedFunctions = this.findMatchingFunctions(prompt)
|
|
50
|
-
const matchedModule = this.findMatchingModule(prompt)
|
|
51
|
-
|
|
52
|
-
for (const action of actions) {
|
|
53
|
-
if (matchedFunctions.length > 0) {
|
|
54
|
-
// Create an intent for each matched function (up to 3)
|
|
55
|
-
for (const fn of matchedFunctions.slice(0, 3)) {
|
|
56
|
-
intents.push({
|
|
57
|
-
action,
|
|
58
|
-
target: {
|
|
59
|
-
type: 'function',
|
|
60
|
-
name: fn.name,
|
|
61
|
-
moduleId: fn.moduleId,
|
|
62
|
-
filePath: fn.file,
|
|
63
|
-
},
|
|
64
|
-
reason: prompt,
|
|
65
|
-
confidence: fn.score,
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
} else if (matchedModule) {
|
|
69
|
-
intents.push({
|
|
70
|
-
action,
|
|
71
|
-
target: {
|
|
72
|
-
type: 'module',
|
|
73
|
-
name: matchedModule.name,
|
|
74
|
-
moduleId: matchedModule.id,
|
|
75
|
-
},
|
|
76
|
-
reason: prompt,
|
|
77
|
-
confidence: matchedModule.score,
|
|
78
|
-
})
|
|
79
|
-
} else {
|
|
80
|
-
// No match — use extracted name
|
|
81
|
-
intents.push({
|
|
82
|
-
action,
|
|
83
|
-
target: {
|
|
84
|
-
type: this.inferTargetType(prompt),
|
|
85
|
-
name: this.extractName(prompt),
|
|
86
|
-
},
|
|
87
|
-
reason: prompt,
|
|
88
|
-
confidence: 0.3,
|
|
89
|
-
})
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return intents
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ── Fuzzy Matching ───────────────────────────────────────────
|
|
97
|
-
|
|
98
|
-
private findMatchingFunctions(prompt: string): Array<{ name: string; file: string; moduleId: string; score: number }> {
|
|
99
|
-
const promptLower = prompt.toLowerCase()
|
|
100
|
-
const keywords = this.extractKeywords(prompt)
|
|
101
|
-
const results: Array<{ name: string; file: string; moduleId: string; score: number }> = []
|
|
102
|
-
|
|
103
|
-
for (const fn of Object.values(this.lock.functions)) {
|
|
104
|
-
let score = 0
|
|
105
|
-
const fnNameLower = fn.name.toLowerCase()
|
|
106
|
-
const fileLower = fn.file.toLowerCase()
|
|
107
|
-
|
|
108
|
-
// Exact name match in prompt → very high score
|
|
109
|
-
if (promptLower.includes(fnNameLower) && fnNameLower.length > 3) {
|
|
110
|
-
score += 0.9
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Keyword matches in function name
|
|
114
|
-
for (const kw of keywords) {
|
|
115
|
-
if (fnNameLower.includes(kw)) score += 0.3
|
|
116
|
-
if (fileLower.includes(kw)) score += 0.15
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// CamelCase decomposition partial matches
|
|
120
|
-
const fnWords = this.splitCamelCase(fn.name).map(w => w.toLowerCase())
|
|
121
|
-
for (const kw of keywords) {
|
|
122
|
-
if (fnWords.some(w => w.startsWith(kw) || kw.startsWith(w))) {
|
|
123
|
-
score += 0.2
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Cap score at 1.0
|
|
128
|
-
if (score > 0.3) {
|
|
129
|
-
results.push({
|
|
130
|
-
name: fn.name,
|
|
131
|
-
file: fn.file,
|
|
132
|
-
moduleId: fn.moduleId,
|
|
133
|
-
score: Math.min(score, 1.0),
|
|
134
|
-
})
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Sort by score descending
|
|
139
|
-
return results.sort((a, b) => b.score - a.score)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
private findMatchingModule(prompt: string): { id: string; name: string; score: number } | null {
|
|
143
|
-
const promptLower = prompt.toLowerCase()
|
|
144
|
-
const keywords = this.extractKeywords(prompt)
|
|
145
|
-
let bestMatch: { id: string; name: string; score: number } | null = null
|
|
146
|
-
|
|
147
|
-
for (const module of this.contract.declared.modules) {
|
|
148
|
-
let score = 0
|
|
149
|
-
const moduleLower = module.name.toLowerCase()
|
|
150
|
-
const moduleIdLower = module.id.toLowerCase()
|
|
151
|
-
|
|
152
|
-
// Direct ID or name match
|
|
153
|
-
if (promptLower.includes(moduleIdLower)) score += 0.8
|
|
154
|
-
if (promptLower.includes(moduleLower)) score += 0.7
|
|
155
|
-
|
|
156
|
-
// Keyword matches
|
|
157
|
-
for (const kw of keywords) {
|
|
158
|
-
if (moduleLower.includes(kw)) score += 0.2
|
|
159
|
-
if (moduleIdLower.includes(kw)) score += 0.2
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (score > (bestMatch?.score || 0)) {
|
|
163
|
-
bestMatch = { id: module.id, name: module.name, score: Math.min(score, 1.0) }
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return bestMatch && bestMatch.score > 0.3 ? bestMatch : null
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// ── Helpers ───────────────────────────────────────────────────
|
|
171
|
-
|
|
172
|
-
private inferTargetType(prompt: string): Intent['target']['type'] {
|
|
173
|
-
const lower = prompt.toLowerCase()
|
|
174
|
-
if (lower.includes('function') || lower.includes('method')) return 'function'
|
|
175
|
-
if (lower.includes('class')) return 'class'
|
|
176
|
-
if (lower.includes('module') || lower.includes('package')) return 'module'
|
|
177
|
-
return 'file'
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
private extractName(prompt: string): string {
|
|
181
|
-
// Try quoted strings first
|
|
182
|
-
const quoted = prompt.match(/["'`]([^"'`]+)["'`]/)
|
|
183
|
-
if (quoted) return quoted[1]
|
|
184
|
-
|
|
185
|
-
// Try backtick code references
|
|
186
|
-
const code = prompt.match(/`([^`]+)`/)
|
|
187
|
-
if (code) return code[1]
|
|
188
|
-
|
|
189
|
-
// Fall back to last meaningful word
|
|
190
|
-
const words = prompt.split(/\s+/).filter(w => w.length > 2)
|
|
191
|
-
return words[words.length - 1] || 'unknown'
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
private extractKeywords(text: string): string[] {
|
|
195
|
-
const stopWords = new Set([
|
|
196
|
-
'the', 'a', 'an', 'in', 'on', 'at', 'to', 'for', 'of', 'and',
|
|
197
|
-
'or', 'is', 'are', 'was', 'be', 'not', 'no', 'from', 'with',
|
|
198
|
-
'add', 'create', 'modify', 'change', 'update', 'delete', 'remove',
|
|
199
|
-
'fix', 'move', 'refactor', 'new', 'old', 'all', 'this', 'that',
|
|
200
|
-
'should', 'can', 'will', 'must', 'need', 'want', 'please',
|
|
201
|
-
])
|
|
202
|
-
return text
|
|
203
|
-
.toLowerCase()
|
|
204
|
-
.replace(/[^a-z0-9\s]/g, ' ')
|
|
205
|
-
.split(/\s+/)
|
|
206
|
-
.filter(w => w.length > 2 && !stopWords.has(w))
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
private splitCamelCase(name: string): string[] {
|
|
210
|
-
return name
|
|
211
|
-
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
212
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
213
|
-
.split(/[\s_-]+/)
|
|
214
|
-
.filter(w => w.length > 0)
|
|
215
|
-
}
|
|
216
|
-
}
|
|
1
|
+
import type { MikkContract, MikkLock } from '@getmikk/core'
|
|
2
|
+
import { IntentSchema, type Intent } from './types.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* IntentInterpreter — parses a natural-language prompt into structured
|
|
6
|
+
* intents using heuristic keyword matching and fuzzy matching against
|
|
7
|
+
* the lock file's function/module names.
|
|
8
|
+
*/
|
|
9
|
+
export class IntentInterpreter {
|
|
10
|
+
constructor(
|
|
11
|
+
private contract: MikkContract,
|
|
12
|
+
private lock: MikkLock
|
|
13
|
+
) { }
|
|
14
|
+
|
|
15
|
+
async interpret(prompt: string): Promise<Intent[]> {
|
|
16
|
+
const intents: Intent[] = []
|
|
17
|
+
const promptLower = prompt.toLowerCase()
|
|
18
|
+
|
|
19
|
+
// Detect action verbs
|
|
20
|
+
const actions: Intent['action'][] = []
|
|
21
|
+
if (promptLower.includes('add') || promptLower.includes('create') ||
|
|
22
|
+
promptLower.includes('new') || promptLower.includes('implement')) {
|
|
23
|
+
actions.push('create')
|
|
24
|
+
}
|
|
25
|
+
if (promptLower.includes('modify') || promptLower.includes('change') ||
|
|
26
|
+
promptLower.includes('update') || promptLower.includes('fix') ||
|
|
27
|
+
promptLower.includes('edit') || promptLower.includes('patch')) {
|
|
28
|
+
actions.push('modify')
|
|
29
|
+
}
|
|
30
|
+
if (promptLower.includes('delete') || promptLower.includes('remove') ||
|
|
31
|
+
promptLower.includes('drop')) {
|
|
32
|
+
actions.push('delete')
|
|
33
|
+
}
|
|
34
|
+
if (promptLower.includes('refactor') || promptLower.includes('restructure') ||
|
|
35
|
+
promptLower.includes('clean up') || promptLower.includes('reorganize')) {
|
|
36
|
+
actions.push('refactor')
|
|
37
|
+
}
|
|
38
|
+
if (promptLower.includes('move') || promptLower.includes('migrate') ||
|
|
39
|
+
promptLower.includes('relocate')) {
|
|
40
|
+
actions.push('move')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Default to modify if no action is detected
|
|
44
|
+
if (actions.length === 0) {
|
|
45
|
+
actions.push('modify')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Find the best matching target — try functions first, then modules
|
|
49
|
+
const matchedFunctions = this.findMatchingFunctions(prompt)
|
|
50
|
+
const matchedModule = this.findMatchingModule(prompt)
|
|
51
|
+
|
|
52
|
+
for (const action of actions) {
|
|
53
|
+
if (matchedFunctions.length > 0) {
|
|
54
|
+
// Create an intent for each matched function (up to 3)
|
|
55
|
+
for (const fn of matchedFunctions.slice(0, 3)) {
|
|
56
|
+
intents.push({
|
|
57
|
+
action,
|
|
58
|
+
target: {
|
|
59
|
+
type: 'function',
|
|
60
|
+
name: fn.name,
|
|
61
|
+
moduleId: fn.moduleId,
|
|
62
|
+
filePath: fn.file,
|
|
63
|
+
},
|
|
64
|
+
reason: prompt,
|
|
65
|
+
confidence: fn.score,
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
} else if (matchedModule) {
|
|
69
|
+
intents.push({
|
|
70
|
+
action,
|
|
71
|
+
target: {
|
|
72
|
+
type: 'module',
|
|
73
|
+
name: matchedModule.name,
|
|
74
|
+
moduleId: matchedModule.id,
|
|
75
|
+
},
|
|
76
|
+
reason: prompt,
|
|
77
|
+
confidence: matchedModule.score,
|
|
78
|
+
})
|
|
79
|
+
} else {
|
|
80
|
+
// No match — use extracted name
|
|
81
|
+
intents.push({
|
|
82
|
+
action,
|
|
83
|
+
target: {
|
|
84
|
+
type: this.inferTargetType(prompt),
|
|
85
|
+
name: this.extractName(prompt),
|
|
86
|
+
},
|
|
87
|
+
reason: prompt,
|
|
88
|
+
confidence: 0.3,
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return intents
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Fuzzy Matching ───────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
private findMatchingFunctions(prompt: string): Array<{ name: string; file: string; moduleId: string; score: number }> {
|
|
99
|
+
const promptLower = prompt.toLowerCase()
|
|
100
|
+
const keywords = this.extractKeywords(prompt)
|
|
101
|
+
const results: Array<{ name: string; file: string; moduleId: string; score: number }> = []
|
|
102
|
+
|
|
103
|
+
for (const fn of Object.values(this.lock.functions)) {
|
|
104
|
+
let score = 0
|
|
105
|
+
const fnNameLower = fn.name.toLowerCase()
|
|
106
|
+
const fileLower = fn.file.toLowerCase()
|
|
107
|
+
|
|
108
|
+
// Exact name match in prompt → very high score
|
|
109
|
+
if (promptLower.includes(fnNameLower) && fnNameLower.length > 3) {
|
|
110
|
+
score += 0.9
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Keyword matches in function name
|
|
114
|
+
for (const kw of keywords) {
|
|
115
|
+
if (fnNameLower.includes(kw)) score += 0.3
|
|
116
|
+
if (fileLower.includes(kw)) score += 0.15
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// CamelCase decomposition partial matches
|
|
120
|
+
const fnWords = this.splitCamelCase(fn.name).map(w => w.toLowerCase())
|
|
121
|
+
for (const kw of keywords) {
|
|
122
|
+
if (fnWords.some(w => w.startsWith(kw) || kw.startsWith(w))) {
|
|
123
|
+
score += 0.2
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Cap score at 1.0
|
|
128
|
+
if (score > 0.3) {
|
|
129
|
+
results.push({
|
|
130
|
+
name: fn.name,
|
|
131
|
+
file: fn.file,
|
|
132
|
+
moduleId: fn.moduleId,
|
|
133
|
+
score: Math.min(score, 1.0),
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Sort by score descending
|
|
139
|
+
return results.sort((a, b) => b.score - a.score)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private findMatchingModule(prompt: string): { id: string; name: string; score: number } | null {
|
|
143
|
+
const promptLower = prompt.toLowerCase()
|
|
144
|
+
const keywords = this.extractKeywords(prompt)
|
|
145
|
+
let bestMatch: { id: string; name: string; score: number } | null = null
|
|
146
|
+
|
|
147
|
+
for (const module of this.contract.declared.modules) {
|
|
148
|
+
let score = 0
|
|
149
|
+
const moduleLower = module.name.toLowerCase()
|
|
150
|
+
const moduleIdLower = module.id.toLowerCase()
|
|
151
|
+
|
|
152
|
+
// Direct ID or name match
|
|
153
|
+
if (promptLower.includes(moduleIdLower)) score += 0.8
|
|
154
|
+
if (promptLower.includes(moduleLower)) score += 0.7
|
|
155
|
+
|
|
156
|
+
// Keyword matches
|
|
157
|
+
for (const kw of keywords) {
|
|
158
|
+
if (moduleLower.includes(kw)) score += 0.2
|
|
159
|
+
if (moduleIdLower.includes(kw)) score += 0.2
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (score > (bestMatch?.score || 0)) {
|
|
163
|
+
bestMatch = { id: module.id, name: module.name, score: Math.min(score, 1.0) }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return bestMatch && bestMatch.score > 0.3 ? bestMatch : null
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── Helpers ───────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
private inferTargetType(prompt: string): Intent['target']['type'] {
|
|
173
|
+
const lower = prompt.toLowerCase()
|
|
174
|
+
if (lower.includes('function') || lower.includes('method')) return 'function'
|
|
175
|
+
if (lower.includes('class')) return 'class'
|
|
176
|
+
if (lower.includes('module') || lower.includes('package')) return 'module'
|
|
177
|
+
return 'file'
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private extractName(prompt: string): string {
|
|
181
|
+
// Try quoted strings first
|
|
182
|
+
const quoted = prompt.match(/["'`]([^"'`]+)["'`]/)
|
|
183
|
+
if (quoted) return quoted[1]
|
|
184
|
+
|
|
185
|
+
// Try backtick code references
|
|
186
|
+
const code = prompt.match(/`([^`]+)`/)
|
|
187
|
+
if (code) return code[1]
|
|
188
|
+
|
|
189
|
+
// Fall back to last meaningful word
|
|
190
|
+
const words = prompt.split(/\s+/).filter(w => w.length > 2)
|
|
191
|
+
return words[words.length - 1] || 'unknown'
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private extractKeywords(text: string): string[] {
|
|
195
|
+
const stopWords = new Set([
|
|
196
|
+
'the', 'a', 'an', 'in', 'on', 'at', 'to', 'for', 'of', 'and',
|
|
197
|
+
'or', 'is', 'are', 'was', 'be', 'not', 'no', 'from', 'with',
|
|
198
|
+
'add', 'create', 'modify', 'change', 'update', 'delete', 'remove',
|
|
199
|
+
'fix', 'move', 'refactor', 'new', 'old', 'all', 'this', 'that',
|
|
200
|
+
'should', 'can', 'will', 'must', 'need', 'want', 'please',
|
|
201
|
+
])
|
|
202
|
+
return text
|
|
203
|
+
.toLowerCase()
|
|
204
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
205
|
+
.split(/\s+/)
|
|
206
|
+
.filter(w => w.length > 2 && !stopWords.has(w))
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private splitCamelCase(name: string): string[] {
|
|
210
|
+
return name
|
|
211
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
212
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
213
|
+
.split(/[\s_-]+/)
|
|
214
|
+
.filter(w => w.length > 0)
|
|
215
|
+
}
|
|
216
|
+
}
|
package/src/preflight.ts
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
import type { MikkContract, MikkLock } from '@getmikk/core'
|
|
2
|
-
import { IntentInterpreter } from './interpreter.js'
|
|
3
|
-
import { ConflictDetector } from './conflict-detector.js'
|
|
4
|
-
import { Suggester } from './suggester.js'
|
|
5
|
-
import type { PreflightResult } from './types.js'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* PreflightPipeline — orchestrates the full intent pipeline:
|
|
9
|
-
* interpret → conflict-detect → suggest.
|
|
10
|
-
* Single function call for the CLI.
|
|
11
|
-
*/
|
|
12
|
-
export class PreflightPipeline {
|
|
13
|
-
private interpreter: IntentInterpreter
|
|
14
|
-
private conflictDetector: ConflictDetector
|
|
15
|
-
private suggester: Suggester
|
|
16
|
-
|
|
17
|
-
constructor(
|
|
18
|
-
private contract: MikkContract,
|
|
19
|
-
private lock: MikkLock
|
|
20
|
-
) {
|
|
21
|
-
this.interpreter = new IntentInterpreter(contract, lock)
|
|
22
|
-
this.conflictDetector = new ConflictDetector(contract, lock)
|
|
23
|
-
this.suggester = new Suggester(contract, lock)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** Run the full preflight pipeline */
|
|
27
|
-
async run(prompt: string): Promise<PreflightResult> {
|
|
28
|
-
// 1. Interpret prompt into structured intents
|
|
29
|
-
const intents = await this.interpreter.interpret(prompt)
|
|
30
|
-
|
|
31
|
-
// 2. Check for conflicts
|
|
32
|
-
const conflicts = this.conflictDetector.detect(intents)
|
|
33
|
-
|
|
34
|
-
// 3. Generate suggestions
|
|
35
|
-
const suggestions = this.suggester.suggest(intents)
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
intents,
|
|
39
|
-
conflicts,
|
|
40
|
-
suggestions,
|
|
41
|
-
approved: !conflicts.hasConflicts,
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
1
|
+
import type { MikkContract, MikkLock } from '@getmikk/core'
|
|
2
|
+
import { IntentInterpreter } from './interpreter.js'
|
|
3
|
+
import { ConflictDetector } from './conflict-detector.js'
|
|
4
|
+
import { Suggester } from './suggester.js'
|
|
5
|
+
import type { PreflightResult } from './types.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* PreflightPipeline — orchestrates the full intent pipeline:
|
|
9
|
+
* interpret → conflict-detect → suggest.
|
|
10
|
+
* Single function call for the CLI.
|
|
11
|
+
*/
|
|
12
|
+
export class PreflightPipeline {
|
|
13
|
+
private interpreter: IntentInterpreter
|
|
14
|
+
private conflictDetector: ConflictDetector
|
|
15
|
+
private suggester: Suggester
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
private contract: MikkContract,
|
|
19
|
+
private lock: MikkLock
|
|
20
|
+
) {
|
|
21
|
+
this.interpreter = new IntentInterpreter(contract, lock)
|
|
22
|
+
this.conflictDetector = new ConflictDetector(contract, lock)
|
|
23
|
+
this.suggester = new Suggester(contract, lock)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Run the full preflight pipeline */
|
|
27
|
+
async run(prompt: string): Promise<PreflightResult> {
|
|
28
|
+
// 1. Interpret prompt into structured intents
|
|
29
|
+
const intents = await this.interpreter.interpret(prompt)
|
|
30
|
+
|
|
31
|
+
// 2. Check for conflicts
|
|
32
|
+
const conflicts = this.conflictDetector.detect(intents)
|
|
33
|
+
|
|
34
|
+
// 3. Generate suggestions
|
|
35
|
+
const suggestions = this.suggester.suggest(intents)
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
intents,
|
|
39
|
+
conflicts,
|
|
40
|
+
suggestions,
|
|
41
|
+
approved: !conflicts.hasConflicts,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|