@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.
@@ -1,302 +1,302 @@
1
- import type { MikkContract, MikkLock } from '@getmikk/core'
2
- import type { Intent, ConflictResult, Conflict } from './types.js'
3
-
4
- /**
5
- * Constraint types — classifies constraints for rule-based checking per Section 5.
6
- * Rule-based matching is fast and doesn't require AI calls.
7
- */
8
- type ConstraintType =
9
- | 'no-import' // "No direct DB access outside db/"
10
- | 'must-use' // "All auth must go through auth.middleware"
11
- | 'no-call' // "Never call setTimeout in the payment flow"
12
- | 'layer' // "Controllers cannot import from repositories directly"
13
- | 'naming' // "All exported functions must be camelCase"
14
- | 'complex' // Everything else
15
-
16
- /**
17
- * ConflictDetector — checks candidate intents against declared
18
- * constraints and module boundaries. Uses rule-based pattern matching
19
- * per spec Section 5 — no AI calls for deterministic constraint types.
20
- */
21
- export class ConflictDetector {
22
- constructor(
23
- private contract: MikkContract,
24
- private lock?: MikkLock
25
- ) { }
26
-
27
- /** Check all intents for conflicts */
28
- detect(intents: Intent[]): ConflictResult {
29
- const conflicts: Conflict[] = []
30
-
31
- for (const intent of intents) {
32
- // Check constraint violations
33
- for (const constraint of this.contract.declared.constraints) {
34
- const conflict = this.checkConstraint(intent, constraint)
35
- if (conflict) {
36
- conflicts.push(conflict)
37
- }
38
- }
39
-
40
- // Check boundary crossings for move/refactor actions
41
- if (intent.target.moduleId && (intent.action === 'move' || intent.action === 'refactor')) {
42
- // Check if the target module exists
43
- const moduleExists = this.contract.declared.modules.some(
44
- m => m.id === intent.target.moduleId
45
- )
46
- if (intent.action === 'move') {
47
- conflicts.push({
48
- type: 'boundary-crossing',
49
- severity: 'warning',
50
- message: `Moving ${intent.target.name} will cross module boundary from ${intent.target.moduleId}`,
51
- relatedIntent: intent,
52
- suggestedFix: moduleExists
53
- ? `Ensure all callers of ${intent.target.name} are updated after the move`
54
- : `Module "${intent.target.moduleId}" not found — check mikk.json`,
55
- })
56
- }
57
- }
58
-
59
- // Check for missing dependency: does the target exist in the lock?
60
- if (this.lock && intent.action === 'modify') {
61
- const fnExists = Object.values(this.lock.functions).some(
62
- f => f.name === intent.target.name
63
- )
64
- const fileExists = intent.target.filePath
65
- ? !!this.lock.files[intent.target.filePath]
66
- : true
67
- if (!fnExists && intent.target.type === 'function') {
68
- conflicts.push({
69
- type: 'missing-dependency',
70
- severity: 'warning',
71
- message: `Function "${intent.target.name}" not found in lock file — it may not exist yet`,
72
- relatedIntent: intent,
73
- suggestedFix: `Did you mean "create" instead of "modify"?`,
74
- })
75
- }
76
- if (!fileExists) {
77
- conflicts.push({
78
- type: 'missing-dependency',
79
- severity: 'warning',
80
- message: `File "${intent.target.filePath}" not found in lock file`,
81
- relatedIntent: intent,
82
- })
83
- }
84
- }
85
-
86
- // Ownership check: warn if modifying a module with explicit owners
87
- if (intent.target.moduleId) {
88
- const module = this.contract.declared.modules.find(
89
- m => m.id === intent.target.moduleId
90
- )
91
- if (module?.owners && module.owners.length > 0) {
92
- conflicts.push({
93
- type: 'ownership-conflict',
94
- severity: 'warning',
95
- message: `Module "${module.name}" has designated owners: ${module.owners.join(', ')}`,
96
- relatedIntent: intent,
97
- suggestedFix: `Coordinate with ${module.owners[0]} before modifying this module`,
98
- })
99
- }
100
- }
101
- }
102
-
103
- return {
104
- hasConflicts: conflicts.some(c => c.severity === 'error'),
105
- conflicts,
106
- }
107
- }
108
-
109
- // ── Constraint Classification & Checking ─────────────────────
110
-
111
- private classifyConstraint(text: string): ConstraintType {
112
- const lower = text.toLowerCase()
113
- if (lower.includes('no direct') || lower.includes('cannot import') ||
114
- lower.includes('must not import')) return 'no-import'
115
- if (lower.includes('must go through') || lower.includes('must use') ||
116
- lower.includes('required')) return 'must-use'
117
- if (lower.includes('never call') || lower.includes('do not call')) return 'no-call'
118
- if (lower.includes('cannot import from') || lower.includes('layer')) return 'layer'
119
- if (lower.includes('must be') && (lower.includes('case') || lower.includes('named')))
120
- return 'naming'
121
- return 'complex'
122
- }
123
-
124
- private checkConstraint(intent: Intent, constraint: string): Conflict | null {
125
- const type = this.classifyConstraint(constraint)
126
- switch (type) {
127
- case 'no-import': return this.checkNoImport(constraint, intent)
128
- case 'must-use': return this.checkMustUse(constraint, intent)
129
- case 'no-call': return this.checkNoCall(constraint, intent)
130
- case 'layer': return this.checkLayer(constraint, intent)
131
- case 'naming': return this.checkNaming(constraint, intent)
132
- case 'complex': return this.checkComplex(constraint, intent)
133
- }
134
- }
135
-
136
- /** "No direct DB access outside db/" */
137
- private checkNoImport(constraint: string, intent: Intent): Conflict | null {
138
- const match = constraint.match(/no direct (\w+) (?:access|import) outside (.+)/i)
139
- if (!match) {
140
- // Fallback: "X cannot import Y" pattern
141
- const alt = constraint.match(/(\w+) (?:cannot|must not) import (?:from )?(\w+)/i)
142
- if (!alt) return null
143
- const [, source, target] = alt
144
- if (intent.target.moduleId?.toLowerCase().includes(target.toLowerCase())) {
145
- return this.makeConflict(constraint, intent, 'error',
146
- `Intent targets ${intent.target.moduleId} which conflicts with import restriction on ${target}`,
147
- `Use the ${source} module's public API instead`)
148
- }
149
- return null
150
- }
151
-
152
- const [, _accessType, allowedPath] = match
153
- const allowed = allowedPath.trim().replace(/[/\\*]*/g, '')
154
- const targetModule = intent.target.moduleId || ''
155
-
156
- // If the intent's target is outside the allowed area and touches restricted module
157
- if (targetModule && !targetModule.toLowerCase().includes(allowed.toLowerCase())) {
158
- return this.makeConflict(constraint, intent, 'error',
159
- `Intent "${intent.action} ${intent.target.name}" in module "${targetModule}" accesses a restricted area. Only "${allowed}" modules may access this.`,
160
- `Route through the ${allowed} module's public API`)
161
- }
162
- return null
163
- }
164
-
165
- /** "All auth must go through auth.middleware" */
166
- private checkMustUse(constraint: string, intent: Intent): Conflict | null {
167
- const match = constraint.match(/all (\w+) must (?:go through|use) (.+)/i)
168
- if (!match) return null
169
- const [, domain, requiredFn] = match
170
-
171
- // Check if the intent touches this domain
172
- const targetLower = intent.target.name.toLowerCase()
173
- const moduleLower = intent.target.moduleId?.toLowerCase() || ''
174
- if (!targetLower.includes(domain.toLowerCase()) &&
175
- !moduleLower.includes(domain.toLowerCase())) {
176
- return null
177
- }
178
-
179
- // If creating or modifying in this domain, warn about required function
180
- if (intent.action === 'create' || intent.action === 'modify') {
181
- return this.makeConflict(constraint, intent, 'warning',
182
- `${intent.action} in the "${domain}" domain requires using ${requiredFn.trim()}`,
183
- `Ensure ${requiredFn.trim()} is called in the ${intent.target.name} flow`)
184
- }
185
- return null
186
- }
187
-
188
- /** "Never call setTimeout in the payment flow" */
189
- private checkNoCall(constraint: string, intent: Intent): Conflict | null {
190
- const match = constraint.match(/(?:never|do not) call (\w+)(?: in (?:the )?(.+))?/i)
191
- if (!match) return null
192
- const [, forbiddenCall, contextArea] = match
193
-
194
- // If a context area is specified, only check intents in that area
195
- if (contextArea) {
196
- const area = contextArea.trim().replace(/\s*flow$/i, '')
197
- const intentModule = intent.target.moduleId?.toLowerCase() || ''
198
- const intentName = intent.target.name.toLowerCase()
199
- if (!intentModule.includes(area.toLowerCase()) &&
200
- !intentName.includes(area.toLowerCase())) {
201
- return null
202
- }
203
- }
204
-
205
- // Warn if creating code that might use the forbidden function
206
- if (intent.action === 'create' || intent.action === 'modify') {
207
- return this.makeConflict(constraint, intent, 'warning',
208
- `Ensure "${forbiddenCall}" is not called in this context`,
209
- `Avoid using ${forbiddenCall} — see constraint: "${constraint}"`)
210
- }
211
- return null
212
- }
213
-
214
- /** "Controllers cannot import from repositories directly" */
215
- private checkLayer(constraint: string, intent: Intent): Conflict | null {
216
- const match = constraint.match(/(\w+) cannot import (?:from )?(\w+)/i)
217
- if (!match) return null
218
- const [, sourceLayer, targetLayer] = match
219
-
220
- const intentModule = intent.target.moduleId?.toLowerCase() || ''
221
- if (intentModule.includes(sourceLayer.toLowerCase()) &&
222
- (intent.action === 'create' || intent.action === 'modify')) {
223
- return this.makeConflict(constraint, intent, 'warning',
224
- `${sourceLayer} layer should not import directly from ${targetLayer}`,
225
- `Use an intermediate service layer between ${sourceLayer} and ${targetLayer}`)
226
- }
227
- return null
228
- }
229
-
230
- /** "All exported functions must be camelCase" */
231
- private checkNaming(constraint: string, intent: Intent): Conflict | null {
232
- if (intent.action !== 'create') return null
233
-
234
- const name = intent.target.name
235
- if (constraint.toLowerCase().includes('camelcase')) {
236
- // Check if name starts with lowercase and has no underscores/hyphens
237
- if (!/^[a-z][a-zA-Z0-9]*$/.test(name) && name.length > 0) {
238
- return this.makeConflict(constraint, intent, 'warning',
239
- `Name "${name}" does not follow camelCase convention`,
240
- `Rename to ${this.toCamelCase(name)}`)
241
- }
242
- }
243
- return null
244
- }
245
-
246
- /** Complex constraints — keyword overlap heuristic (no AI call) */
247
- private checkComplex(constraint: string, intent: Intent): Conflict | null {
248
- const constraintWords = this.extractKeywords(constraint)
249
- const intentWords = [
250
- ...this.extractKeywords(intent.target.name),
251
- ...this.extractKeywords(intent.reason)
252
- ]
253
- const overlap = constraintWords.filter(w =>
254
- intentWords.some(iw => iw === w || iw.includes(w) || w.includes(iw))
255
- )
256
-
257
- // Only flag if significant keyword overlap suggests relevance
258
- if (overlap.length >= 2) {
259
- return this.makeConflict(constraint, intent, 'warning',
260
- `Intent may conflict with constraint: "${constraint}" (keywords: ${overlap.join(', ')})`,
261
- `Review the constraint before proceeding`)
262
- }
263
- return null
264
- }
265
-
266
- // ── Helpers ───────────────────────────────────────────────────
267
-
268
- private makeConflict(
269
- constraint: string,
270
- intent: Intent,
271
- severity: 'error' | 'warning',
272
- message: string,
273
- suggestedFix?: string
274
- ): Conflict {
275
- return {
276
- type: 'constraint-violation',
277
- severity,
278
- message,
279
- relatedIntent: intent,
280
- suggestedFix,
281
- }
282
- }
283
-
284
- private extractKeywords(text: string): string[] {
285
- const stopWords = new Set([
286
- 'the', 'a', 'an', 'in', 'on', 'at', 'to', 'for', 'of', 'and',
287
- 'or', 'is', 'are', 'was', 'be', 'not', 'no', 'from', 'with',
288
- 'all', 'must', 'should', 'can', 'cannot', 'will', 'this', 'that',
289
- ])
290
- return text
291
- .toLowerCase()
292
- .replace(/[^a-z0-9\s]/g, ' ')
293
- .split(/\s+/)
294
- .filter(w => w.length > 2 && !stopWords.has(w))
295
- }
296
-
297
- private toCamelCase(name: string): string {
298
- return name
299
- .replace(/[-_]+(.)/g, (_, c) => c.toUpperCase())
300
- .replace(/^[A-Z]/, c => c.toLowerCase())
301
- }
302
- }
1
+ import type { MikkContract, MikkLock } from '@getmikk/core'
2
+ import type { Intent, ConflictResult, Conflict } from './types.js'
3
+
4
+ /**
5
+ * Constraint types — classifies constraints for rule-based checking per Section 5.
6
+ * Rule-based matching is fast and doesn't require AI calls.
7
+ */
8
+ type ConstraintType =
9
+ | 'no-import' // "No direct DB access outside db/"
10
+ | 'must-use' // "All auth must go through auth.middleware"
11
+ | 'no-call' // "Never call setTimeout in the payment flow"
12
+ | 'layer' // "Controllers cannot import from repositories directly"
13
+ | 'naming' // "All exported functions must be camelCase"
14
+ | 'complex' // Everything else
15
+
16
+ /**
17
+ * ConflictDetector — checks candidate intents against declared
18
+ * constraints and module boundaries. Uses rule-based pattern matching
19
+ * per spec Section 5 — no AI calls for deterministic constraint types.
20
+ */
21
+ export class ConflictDetector {
22
+ constructor(
23
+ private contract: MikkContract,
24
+ private lock?: MikkLock
25
+ ) { }
26
+
27
+ /** Check all intents for conflicts */
28
+ detect(intents: Intent[]): ConflictResult {
29
+ const conflicts: Conflict[] = []
30
+
31
+ for (const intent of intents) {
32
+ // Check constraint violations
33
+ for (const constraint of this.contract.declared.constraints) {
34
+ const conflict = this.checkConstraint(intent, constraint)
35
+ if (conflict) {
36
+ conflicts.push(conflict)
37
+ }
38
+ }
39
+
40
+ // Check boundary crossings for move/refactor actions
41
+ if (intent.target.moduleId && (intent.action === 'move' || intent.action === 'refactor')) {
42
+ // Check if the target module exists
43
+ const moduleExists = this.contract.declared.modules.some(
44
+ m => m.id === intent.target.moduleId
45
+ )
46
+ if (intent.action === 'move') {
47
+ conflicts.push({
48
+ type: 'boundary-crossing',
49
+ severity: 'warning',
50
+ message: `Moving ${intent.target.name} will cross module boundary from ${intent.target.moduleId}`,
51
+ relatedIntent: intent,
52
+ suggestedFix: moduleExists
53
+ ? `Ensure all callers of ${intent.target.name} are updated after the move`
54
+ : `Module "${intent.target.moduleId}" not found — check mikk.json`,
55
+ })
56
+ }
57
+ }
58
+
59
+ // Check for missing dependency: does the target exist in the lock?
60
+ if (this.lock && intent.action === 'modify') {
61
+ const fnExists = Object.values(this.lock.functions).some(
62
+ f => f.name === intent.target.name
63
+ )
64
+ const fileExists = intent.target.filePath
65
+ ? !!this.lock.files[intent.target.filePath]
66
+ : true
67
+ if (!fnExists && intent.target.type === 'function') {
68
+ conflicts.push({
69
+ type: 'missing-dependency',
70
+ severity: 'warning',
71
+ message: `Function "${intent.target.name}" not found in lock file — it may not exist yet`,
72
+ relatedIntent: intent,
73
+ suggestedFix: `Did you mean "create" instead of "modify"?`,
74
+ })
75
+ }
76
+ if (!fileExists) {
77
+ conflicts.push({
78
+ type: 'missing-dependency',
79
+ severity: 'warning',
80
+ message: `File "${intent.target.filePath}" not found in lock file`,
81
+ relatedIntent: intent,
82
+ })
83
+ }
84
+ }
85
+
86
+ // Ownership check: warn if modifying a module with explicit owners
87
+ if (intent.target.moduleId) {
88
+ const module = this.contract.declared.modules.find(
89
+ m => m.id === intent.target.moduleId
90
+ )
91
+ if (module?.owners && module.owners.length > 0) {
92
+ conflicts.push({
93
+ type: 'ownership-conflict',
94
+ severity: 'warning',
95
+ message: `Module "${module.name}" has designated owners: ${module.owners.join(', ')}`,
96
+ relatedIntent: intent,
97
+ suggestedFix: `Coordinate with ${module.owners[0]} before modifying this module`,
98
+ })
99
+ }
100
+ }
101
+ }
102
+
103
+ return {
104
+ hasConflicts: conflicts.some(c => c.severity === 'error'),
105
+ conflicts,
106
+ }
107
+ }
108
+
109
+ // ── Constraint Classification & Checking ─────────────────────
110
+
111
+ private classifyConstraint(text: string): ConstraintType {
112
+ const lower = text.toLowerCase()
113
+ if (lower.includes('no direct') || lower.includes('cannot import') ||
114
+ lower.includes('must not import')) return 'no-import'
115
+ if (lower.includes('must go through') || lower.includes('must use') ||
116
+ lower.includes('required')) return 'must-use'
117
+ if (lower.includes('never call') || lower.includes('do not call')) return 'no-call'
118
+ if (lower.includes('cannot import from') || lower.includes('layer')) return 'layer'
119
+ if (lower.includes('must be') && (lower.includes('case') || lower.includes('named')))
120
+ return 'naming'
121
+ return 'complex'
122
+ }
123
+
124
+ private checkConstraint(intent: Intent, constraint: string): Conflict | null {
125
+ const type = this.classifyConstraint(constraint)
126
+ switch (type) {
127
+ case 'no-import': return this.checkNoImport(constraint, intent)
128
+ case 'must-use': return this.checkMustUse(constraint, intent)
129
+ case 'no-call': return this.checkNoCall(constraint, intent)
130
+ case 'layer': return this.checkLayer(constraint, intent)
131
+ case 'naming': return this.checkNaming(constraint, intent)
132
+ case 'complex': return this.checkComplex(constraint, intent)
133
+ }
134
+ }
135
+
136
+ /** "No direct DB access outside db/" */
137
+ private checkNoImport(constraint: string, intent: Intent): Conflict | null {
138
+ const match = constraint.match(/no direct (\w+) (?:access|import) outside (.+)/i)
139
+ if (!match) {
140
+ // Fallback: "X cannot import Y" pattern
141
+ const alt = constraint.match(/(\w+) (?:cannot|must not) import (?:from )?(\w+)/i)
142
+ if (!alt) return null
143
+ const [, source, target] = alt
144
+ if (intent.target.moduleId?.toLowerCase().includes(target.toLowerCase())) {
145
+ return this.makeConflict(constraint, intent, 'error',
146
+ `Intent targets ${intent.target.moduleId} which conflicts with import restriction on ${target}`,
147
+ `Use the ${source} module's public API instead`)
148
+ }
149
+ return null
150
+ }
151
+
152
+ const [, _accessType, allowedPath] = match
153
+ const allowed = allowedPath.trim().replace(/[/\\*]*/g, '')
154
+ const targetModule = intent.target.moduleId || ''
155
+
156
+ // If the intent's target is outside the allowed area and touches restricted module
157
+ if (targetModule && !targetModule.toLowerCase().includes(allowed.toLowerCase())) {
158
+ return this.makeConflict(constraint, intent, 'error',
159
+ `Intent "${intent.action} ${intent.target.name}" in module "${targetModule}" accesses a restricted area. Only "${allowed}" modules may access this.`,
160
+ `Route through the ${allowed} module's public API`)
161
+ }
162
+ return null
163
+ }
164
+
165
+ /** "All auth must go through auth.middleware" */
166
+ private checkMustUse(constraint: string, intent: Intent): Conflict | null {
167
+ const match = constraint.match(/all (\w+) must (?:go through|use) (.+)/i)
168
+ if (!match) return null
169
+ const [, domain, requiredFn] = match
170
+
171
+ // Check if the intent touches this domain
172
+ const targetLower = intent.target.name.toLowerCase()
173
+ const moduleLower = intent.target.moduleId?.toLowerCase() || ''
174
+ if (!targetLower.includes(domain.toLowerCase()) &&
175
+ !moduleLower.includes(domain.toLowerCase())) {
176
+ return null
177
+ }
178
+
179
+ // If creating or modifying in this domain, warn about required function
180
+ if (intent.action === 'create' || intent.action === 'modify') {
181
+ return this.makeConflict(constraint, intent, 'warning',
182
+ `${intent.action} in the "${domain}" domain requires using ${requiredFn.trim()}`,
183
+ `Ensure ${requiredFn.trim()} is called in the ${intent.target.name} flow`)
184
+ }
185
+ return null
186
+ }
187
+
188
+ /** "Never call setTimeout in the payment flow" */
189
+ private checkNoCall(constraint: string, intent: Intent): Conflict | null {
190
+ const match = constraint.match(/(?:never|do not) call (\w+)(?: in (?:the )?(.+))?/i)
191
+ if (!match) return null
192
+ const [, forbiddenCall, contextArea] = match
193
+
194
+ // If a context area is specified, only check intents in that area
195
+ if (contextArea) {
196
+ const area = contextArea.trim().replace(/\s*flow$/i, '')
197
+ const intentModule = intent.target.moduleId?.toLowerCase() || ''
198
+ const intentName = intent.target.name.toLowerCase()
199
+ if (!intentModule.includes(area.toLowerCase()) &&
200
+ !intentName.includes(area.toLowerCase())) {
201
+ return null
202
+ }
203
+ }
204
+
205
+ // Warn if creating code that might use the forbidden function
206
+ if (intent.action === 'create' || intent.action === 'modify') {
207
+ return this.makeConflict(constraint, intent, 'warning',
208
+ `Ensure "${forbiddenCall}" is not called in this context`,
209
+ `Avoid using ${forbiddenCall} — see constraint: "${constraint}"`)
210
+ }
211
+ return null
212
+ }
213
+
214
+ /** "Controllers cannot import from repositories directly" */
215
+ private checkLayer(constraint: string, intent: Intent): Conflict | null {
216
+ const match = constraint.match(/(\w+) cannot import (?:from )?(\w+)/i)
217
+ if (!match) return null
218
+ const [, sourceLayer, targetLayer] = match
219
+
220
+ const intentModule = intent.target.moduleId?.toLowerCase() || ''
221
+ if (intentModule.includes(sourceLayer.toLowerCase()) &&
222
+ (intent.action === 'create' || intent.action === 'modify')) {
223
+ return this.makeConflict(constraint, intent, 'warning',
224
+ `${sourceLayer} layer should not import directly from ${targetLayer}`,
225
+ `Use an intermediate service layer between ${sourceLayer} and ${targetLayer}`)
226
+ }
227
+ return null
228
+ }
229
+
230
+ /** "All exported functions must be camelCase" */
231
+ private checkNaming(constraint: string, intent: Intent): Conflict | null {
232
+ if (intent.action !== 'create') return null
233
+
234
+ const name = intent.target.name
235
+ if (constraint.toLowerCase().includes('camelcase')) {
236
+ // Check if name starts with lowercase and has no underscores/hyphens
237
+ if (!/^[a-z][a-zA-Z0-9]*$/.test(name) && name.length > 0) {
238
+ return this.makeConflict(constraint, intent, 'warning',
239
+ `Name "${name}" does not follow camelCase convention`,
240
+ `Rename to ${this.toCamelCase(name)}`)
241
+ }
242
+ }
243
+ return null
244
+ }
245
+
246
+ /** Complex constraints — keyword overlap heuristic (no AI call) */
247
+ private checkComplex(constraint: string, intent: Intent): Conflict | null {
248
+ const constraintWords = this.extractKeywords(constraint)
249
+ const intentWords = [
250
+ ...this.extractKeywords(intent.target.name),
251
+ ...this.extractKeywords(intent.reason)
252
+ ]
253
+ const overlap = constraintWords.filter(w =>
254
+ intentWords.some(iw => iw === w || iw.includes(w) || w.includes(iw))
255
+ )
256
+
257
+ // Only flag if significant keyword overlap suggests relevance
258
+ if (overlap.length >= 2) {
259
+ return this.makeConflict(constraint, intent, 'warning',
260
+ `Intent may conflict with constraint: "${constraint}" (keywords: ${overlap.join(', ')})`,
261
+ `Review the constraint before proceeding`)
262
+ }
263
+ return null
264
+ }
265
+
266
+ // ── Helpers ───────────────────────────────────────────────────
267
+
268
+ private makeConflict(
269
+ constraint: string,
270
+ intent: Intent,
271
+ severity: 'error' | 'warning',
272
+ message: string,
273
+ suggestedFix?: string
274
+ ): Conflict {
275
+ return {
276
+ type: 'constraint-violation',
277
+ severity,
278
+ message,
279
+ relatedIntent: intent,
280
+ suggestedFix,
281
+ }
282
+ }
283
+
284
+ private extractKeywords(text: string): string[] {
285
+ const stopWords = new Set([
286
+ 'the', 'a', 'an', 'in', 'on', 'at', 'to', 'for', 'of', 'and',
287
+ 'or', 'is', 'are', 'was', 'be', 'not', 'no', 'from', 'with',
288
+ 'all', 'must', 'should', 'can', 'cannot', 'will', 'this', 'that',
289
+ ])
290
+ return text
291
+ .toLowerCase()
292
+ .replace(/[^a-z0-9\s]/g, ' ')
293
+ .split(/\s+/)
294
+ .filter(w => w.length > 2 && !stopWords.has(w))
295
+ }
296
+
297
+ private toCamelCase(name: string): string {
298
+ return name
299
+ .replace(/[-_]+(.)/g, (_, c) => c.toUpperCase())
300
+ .replace(/^[A-Z]/, c => c.toLowerCase())
301
+ }
302
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { IntentInterpreter } from './interpreter.js'
2
- export { ConflictDetector } from './conflict-detector.js'
3
- export { Suggester } from './suggester.js'
4
- export { PreflightPipeline } from './preflight.js'
5
- export type { Intent, Conflict, ConflictResult, Suggestion, PreflightResult, AIProviderConfig } from './types.js'
6
- export { IntentSchema } from './types.js'
1
+ export { IntentInterpreter } from './interpreter.js'
2
+ export { ConflictDetector } from './conflict-detector.js'
3
+ export { Suggester } from './suggester.js'
4
+ export { PreflightPipeline } from './preflight.js'
5
+ export type { Intent, Conflict, ConflictResult, Suggestion, PreflightResult, AIProviderConfig } from './types.js'
6
+ export { IntentSchema } from './types.js'