@getmikk/intent-engine 1.2.0 → 1.3.1

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,210 +1,210 @@
1
- import { describe, test, expect } from 'bun:test'
2
- import { IntentInterpreter } from '../src/interpreter'
3
- import { ConflictDetector } from '../src/conflict-detector'
4
- import { PreflightPipeline } from '../src/preflight'
5
- import type { MikkContract, MikkLock } from '@getmikk/core'
6
-
7
- const mockContract: MikkContract = {
8
- version: '1.0.0',
9
- project: {
10
- name: 'TestProject',
11
- description: 'Test',
12
- language: 'TypeScript',
13
- entryPoints: ['src/index.ts'],
14
- },
15
- declared: {
16
- modules: [
17
- { id: 'auth', name: 'Authentication', description: 'Auth module', paths: ['src/auth/**'], owners: ['alice'] },
18
- { id: 'api', name: 'API', description: 'API layer', paths: ['src/api/**'] },
19
- { id: 'db', name: 'Database', description: 'DB layer', paths: ['src/db/**'] },
20
- ],
21
- constraints: [
22
- 'No direct DB access outside db/',
23
- 'All auth must go through auth.middleware',
24
- 'Controllers cannot import from repositories directly',
25
- 'Never call setTimeout in the payment flow',
26
- ],
27
- decisions: [
28
- { id: 'd1', title: 'Use JWT', reason: 'Stateless auth', date: '2024-01-01' },
29
- ],
30
- },
31
- overwrite: { mode: 'never', requireConfirmation: true },
32
- }
33
-
34
- const mockLock: MikkLock = {
35
- version: '1.0.0',
36
- generatedAt: new Date().toISOString(),
37
- generatorVersion: '1.1.0',
38
- projectRoot: '/test',
39
- syncState: { status: 'clean', lastSyncAt: new Date().toISOString(), lockHash: 'a', contractHash: 'b' },
40
- modules: {
41
- auth: { id: 'auth', files: ['src/auth/verify.ts'], hash: 'h1', fragmentPath: '.mikk/fragments/auth.json' },
42
- api: { id: 'api', files: ['src/api/login.ts'], hash: 'h2', fragmentPath: '.mikk/fragments/api.json' },
43
- },
44
- functions: {
45
- 'fn:auth:verifyToken': {
46
- id: 'fn:auth:verifyToken', name: 'verifyToken', file: 'src/auth/verify.ts',
47
- startLine: 1, endLine: 10, hash: 'h1', calls: [], calledBy: ['fn:api:handleLogin'],
48
- moduleId: 'auth',
49
- },
50
- 'fn:api:handleLogin': {
51
- id: 'fn:api:handleLogin', name: 'handleLogin', file: 'src/api/login.ts',
52
- startLine: 1, endLine: 20, hash: 'h3', calls: ['fn:auth:verifyToken'], calledBy: [],
53
- moduleId: 'api',
54
- },
55
- },
56
- files: {
57
- 'src/auth/verify.ts': { path: 'src/auth/verify.ts', hash: 'fh1', moduleId: 'auth', lastModified: new Date().toISOString() },
58
- 'src/api/login.ts': { path: 'src/api/login.ts', hash: 'fh2', moduleId: 'api', lastModified: new Date().toISOString() },
59
- },
60
- graph: { nodes: 2, edges: 1, rootHash: 'root' },
61
- }
62
-
63
- describe('IntentInterpreter', () => {
64
- const interpreter = new IntentInterpreter(mockContract, mockLock)
65
-
66
- test('detects create action', async () => {
67
- const intents = await interpreter.interpret('create a new auth handler')
68
- expect(intents.some(i => i.action === 'create')).toBe(true)
69
- })
70
-
71
- test('detects modify action from "fix"', async () => {
72
- const intents = await interpreter.interpret('fix the verifyToken function')
73
- expect(intents.some(i => i.action === 'modify')).toBe(true)
74
- })
75
-
76
- test('matches function by name', async () => {
77
- const intents = await interpreter.interpret('update verifyToken to use async')
78
- const modifyIntent = intents.find(i => i.action === 'modify')
79
- expect(modifyIntent?.target.name).toBe('verifyToken')
80
- expect(modifyIntent?.target.type).toBe('function')
81
- })
82
-
83
- test('matches module by name', async () => {
84
- const intents = await interpreter.interpret('refactor the Authentication module')
85
- const intent = intents.find(i => i.action === 'refactor')
86
- expect(intent?.target.type).toBe('module')
87
- expect(intent?.target.moduleId).toBe('auth')
88
- })
89
-
90
- test('defaults to modify when no action keyword', async () => {
91
- const intents = await interpreter.interpret('something about verifyToken')
92
- expect(intents[0].action).toBe('modify')
93
- })
94
-
95
- test('fuzzy matches camelCase components', async () => {
96
- const intents = await interpreter.interpret('update the token verification')
97
- // Should match verifyToken via "token" + "verify" keyword overlap
98
- const fns = intents.filter(i => i.target.type === 'function')
99
- expect(fns.length).toBeGreaterThanOrEqual(0) // at least attempts matching
100
- })
101
- })
102
-
103
- describe('ConflictDetector', () => {
104
- test('detects no-import constraint violations', () => {
105
- const detector = new ConflictDetector(mockContract, mockLock)
106
- const result = detector.detect([{
107
- action: 'modify',
108
- target: { type: 'function', name: 'handleLogin', moduleId: 'api', filePath: 'src/api/login.ts' },
109
- reason: 'Access DB directly from API',
110
- confidence: 0.8,
111
- }])
112
- // The "No direct DB access outside db/" constraint should fire
113
- const dbConflict = result.conflicts.find(c =>
114
- c.message.toLowerCase().includes('db') || c.message.toLowerCase().includes('restricted')
115
- )
116
- // This may or may not fire depending on exact matching — test the shape
117
- expect(result.conflicts).toBeInstanceOf(Array)
118
- })
119
-
120
- test('detects boundary crossing on move', () => {
121
- const detector = new ConflictDetector(mockContract, mockLock)
122
- const result = detector.detect([{
123
- action: 'move',
124
- target: { type: 'function', name: 'verifyToken', moduleId: 'auth' },
125
- reason: 'Move to API module',
126
- confidence: 0.8,
127
- }])
128
- const crossing = result.conflicts.find(c => c.type === 'boundary-crossing')
129
- expect(crossing).toBeDefined()
130
- expect(crossing!.message).toContain('verifyToken')
131
- })
132
-
133
- test('detects missing function on modify', () => {
134
- const detector = new ConflictDetector(mockContract, mockLock)
135
- const result = detector.detect([{
136
- action: 'modify',
137
- target: { type: 'function', name: 'nonExistentFunction' },
138
- reason: 'Fix something',
139
- confidence: 0.3,
140
- }])
141
- const missing = result.conflicts.find(c => c.type === 'missing-dependency')
142
- expect(missing).toBeDefined()
143
- expect(missing!.message).toContain('nonExistentFunction')
144
- })
145
-
146
- test('detects ownership warning', () => {
147
- const detector = new ConflictDetector(mockContract, mockLock)
148
- const result = detector.detect([{
149
- action: 'modify',
150
- target: { type: 'function', name: 'verifyToken', moduleId: 'auth' },
151
- reason: 'Modify auth',
152
- confidence: 0.8,
153
- }])
154
- const ownership = result.conflicts.find(c => c.type === 'ownership-conflict')
155
- expect(ownership).toBeDefined()
156
- expect(ownership!.message).toContain('alice')
157
- })
158
-
159
- test('must-use constraint triggers for auth domain', () => {
160
- const detector = new ConflictDetector(mockContract, mockLock)
161
- const result = detector.detect([{
162
- action: 'create',
163
- target: { type: 'function', name: 'authHandler', moduleId: 'auth' },
164
- reason: 'New auth handler',
165
- confidence: 0.7,
166
- }])
167
- const mustUse = result.conflicts.find(c =>
168
- c.message.includes('auth.middleware') || c.message.includes('must')
169
- )
170
- expect(mustUse).toBeDefined()
171
- })
172
-
173
- test('hasConflicts is false when only warnings', () => {
174
- const detector = new ConflictDetector(mockContract, mockLock)
175
- const result = detector.detect([{
176
- action: 'modify',
177
- target: { type: 'function', name: 'verifyToken', moduleId: 'auth' },
178
- reason: 'Small fix',
179
- confidence: 0.8,
180
- }])
181
- // Ownership warnings shouldn't block (severity: warning, not error)
182
- // hasConflicts should be false unless there's an error severity
183
- const hasErrors = result.conflicts.some(c => c.severity === 'error')
184
- expect(result.hasConflicts).toBe(hasErrors)
185
- })
186
- })
187
-
188
- describe('PreflightPipeline', () => {
189
- test('full pipeline produces valid result', async () => {
190
- const pipeline = new PreflightPipeline(mockContract, mockLock)
191
- const result = await pipeline.run('fix the verifyToken function')
192
-
193
- expect(result.intents.length).toBeGreaterThan(0)
194
- expect(result.conflicts).toBeDefined()
195
- expect(result.conflicts.conflicts).toBeInstanceOf(Array)
196
- expect(result.suggestions).toBeInstanceOf(Array)
197
- expect(result.suggestions.length).toBeGreaterThan(0)
198
- expect(typeof result.approved).toBe('boolean')
199
- })
200
-
201
- test('suggestions include affected files', async () => {
202
- const pipeline = new PreflightPipeline(mockContract, mockLock)
203
- const result = await pipeline.run('modify verifyToken')
204
-
205
- const suggestion = result.suggestions.find(s =>
206
- s.affectedFiles.some(f => f.includes('verify'))
207
- )
208
- expect(suggestion).toBeDefined()
209
- })
210
- })
1
+ import { describe, test, expect } from 'bun:test'
2
+ import { IntentInterpreter } from '../src/interpreter'
3
+ import { ConflictDetector } from '../src/conflict-detector'
4
+ import { PreflightPipeline } from '../src/preflight'
5
+ import type { MikkContract, MikkLock } from '@getmikk/core'
6
+
7
+ const mockContract: MikkContract = {
8
+ version: '1.0.0',
9
+ project: {
10
+ name: 'TestProject',
11
+ description: 'Test',
12
+ language: 'TypeScript',
13
+ entryPoints: ['src/index.ts'],
14
+ },
15
+ declared: {
16
+ modules: [
17
+ { id: 'auth', name: 'Authentication', description: 'Auth module', paths: ['src/auth/**'], owners: ['alice'] },
18
+ { id: 'api', name: 'API', description: 'API layer', paths: ['src/api/**'] },
19
+ { id: 'db', name: 'Database', description: 'DB layer', paths: ['src/db/**'] },
20
+ ],
21
+ constraints: [
22
+ 'No direct DB access outside db/',
23
+ 'All auth must go through auth.middleware',
24
+ 'Controllers cannot import from repositories directly',
25
+ 'Never call setTimeout in the payment flow',
26
+ ],
27
+ decisions: [
28
+ { id: 'd1', title: 'Use JWT', reason: 'Stateless auth', date: '2024-01-01' },
29
+ ],
30
+ },
31
+ overwrite: { mode: 'never', requireConfirmation: true },
32
+ }
33
+
34
+ const mockLock: MikkLock = {
35
+ version: '1.0.0',
36
+ generatedAt: new Date().toISOString(),
37
+ generatorVersion: '1.1.0',
38
+ projectRoot: '/test',
39
+ syncState: { status: 'clean', lastSyncAt: new Date().toISOString(), lockHash: 'a', contractHash: 'b' },
40
+ modules: {
41
+ auth: { id: 'auth', files: ['src/auth/verify.ts'], hash: 'h1', fragmentPath: '.mikk/fragments/auth.json' },
42
+ api: { id: 'api', files: ['src/api/login.ts'], hash: 'h2', fragmentPath: '.mikk/fragments/api.json' },
43
+ },
44
+ functions: {
45
+ 'fn:auth:verifyToken': {
46
+ id: 'fn:auth:verifyToken', name: 'verifyToken', file: 'src/auth/verify.ts',
47
+ startLine: 1, endLine: 10, hash: 'h1', calls: [], calledBy: ['fn:api:handleLogin'],
48
+ moduleId: 'auth',
49
+ },
50
+ 'fn:api:handleLogin': {
51
+ id: 'fn:api:handleLogin', name: 'handleLogin', file: 'src/api/login.ts',
52
+ startLine: 1, endLine: 20, hash: 'h3', calls: ['fn:auth:verifyToken'], calledBy: [],
53
+ moduleId: 'api',
54
+ },
55
+ },
56
+ files: {
57
+ 'src/auth/verify.ts': { path: 'src/auth/verify.ts', hash: 'fh1', moduleId: 'auth', lastModified: new Date().toISOString() },
58
+ 'src/api/login.ts': { path: 'src/api/login.ts', hash: 'fh2', moduleId: 'api', lastModified: new Date().toISOString() },
59
+ },
60
+ graph: { nodes: 2, edges: 1, rootHash: 'root' },
61
+ }
62
+
63
+ describe('IntentInterpreter', () => {
64
+ const interpreter = new IntentInterpreter(mockContract, mockLock)
65
+
66
+ test('detects create action', async () => {
67
+ const intents = await interpreter.interpret('create a new auth handler')
68
+ expect(intents.some(i => i.action === 'create')).toBe(true)
69
+ })
70
+
71
+ test('detects modify action from "fix"', async () => {
72
+ const intents = await interpreter.interpret('fix the verifyToken function')
73
+ expect(intents.some(i => i.action === 'modify')).toBe(true)
74
+ })
75
+
76
+ test('matches function by name', async () => {
77
+ const intents = await interpreter.interpret('update verifyToken to use async')
78
+ const modifyIntent = intents.find(i => i.action === 'modify')
79
+ expect(modifyIntent?.target.name).toBe('verifyToken')
80
+ expect(modifyIntent?.target.type).toBe('function')
81
+ })
82
+
83
+ test('matches module by name', async () => {
84
+ const intents = await interpreter.interpret('refactor the Authentication module')
85
+ const intent = intents.find(i => i.action === 'refactor')
86
+ expect(intent?.target.type).toBe('module')
87
+ expect(intent?.target.moduleId).toBe('auth')
88
+ })
89
+
90
+ test('defaults to modify when no action keyword', async () => {
91
+ const intents = await interpreter.interpret('something about verifyToken')
92
+ expect(intents[0].action).toBe('modify')
93
+ })
94
+
95
+ test('fuzzy matches camelCase components', async () => {
96
+ const intents = await interpreter.interpret('update the token verification')
97
+ // Should match verifyToken via "token" + "verify" keyword overlap
98
+ const fns = intents.filter(i => i.target.type === 'function')
99
+ expect(fns.length).toBeGreaterThanOrEqual(0) // at least attempts matching
100
+ })
101
+ })
102
+
103
+ describe('ConflictDetector', () => {
104
+ test('detects no-import constraint violations', () => {
105
+ const detector = new ConflictDetector(mockContract, mockLock)
106
+ const result = detector.detect([{
107
+ action: 'modify',
108
+ target: { type: 'function', name: 'handleLogin', moduleId: 'api', filePath: 'src/api/login.ts' },
109
+ reason: 'Access DB directly from API',
110
+ confidence: 0.8,
111
+ }])
112
+ // The "No direct DB access outside db/" constraint should fire
113
+ const dbConflict = result.conflicts.find(c =>
114
+ c.message.toLowerCase().includes('db') || c.message.toLowerCase().includes('restricted')
115
+ )
116
+ // This may or may not fire depending on exact matching — test the shape
117
+ expect(result.conflicts).toBeInstanceOf(Array)
118
+ })
119
+
120
+ test('detects boundary crossing on move', () => {
121
+ const detector = new ConflictDetector(mockContract, mockLock)
122
+ const result = detector.detect([{
123
+ action: 'move',
124
+ target: { type: 'function', name: 'verifyToken', moduleId: 'auth' },
125
+ reason: 'Move to API module',
126
+ confidence: 0.8,
127
+ }])
128
+ const crossing = result.conflicts.find(c => c.type === 'boundary-crossing')
129
+ expect(crossing).toBeDefined()
130
+ expect(crossing!.message).toContain('verifyToken')
131
+ })
132
+
133
+ test('detects missing function on modify', () => {
134
+ const detector = new ConflictDetector(mockContract, mockLock)
135
+ const result = detector.detect([{
136
+ action: 'modify',
137
+ target: { type: 'function', name: 'nonExistentFunction' },
138
+ reason: 'Fix something',
139
+ confidence: 0.3,
140
+ }])
141
+ const missing = result.conflicts.find(c => c.type === 'missing-dependency')
142
+ expect(missing).toBeDefined()
143
+ expect(missing!.message).toContain('nonExistentFunction')
144
+ })
145
+
146
+ test('detects ownership warning', () => {
147
+ const detector = new ConflictDetector(mockContract, mockLock)
148
+ const result = detector.detect([{
149
+ action: 'modify',
150
+ target: { type: 'function', name: 'verifyToken', moduleId: 'auth' },
151
+ reason: 'Modify auth',
152
+ confidence: 0.8,
153
+ }])
154
+ const ownership = result.conflicts.find(c => c.type === 'ownership-conflict')
155
+ expect(ownership).toBeDefined()
156
+ expect(ownership!.message).toContain('alice')
157
+ })
158
+
159
+ test('must-use constraint triggers for auth domain', () => {
160
+ const detector = new ConflictDetector(mockContract, mockLock)
161
+ const result = detector.detect([{
162
+ action: 'create',
163
+ target: { type: 'function', name: 'authHandler', moduleId: 'auth' },
164
+ reason: 'New auth handler',
165
+ confidence: 0.7,
166
+ }])
167
+ const mustUse = result.conflicts.find(c =>
168
+ c.message.includes('auth.middleware') || c.message.includes('must')
169
+ )
170
+ expect(mustUse).toBeDefined()
171
+ })
172
+
173
+ test('hasConflicts is false when only warnings', () => {
174
+ const detector = new ConflictDetector(mockContract, mockLock)
175
+ const result = detector.detect([{
176
+ action: 'modify',
177
+ target: { type: 'function', name: 'verifyToken', moduleId: 'auth' },
178
+ reason: 'Small fix',
179
+ confidence: 0.8,
180
+ }])
181
+ // Ownership warnings shouldn't block (severity: warning, not error)
182
+ // hasConflicts should be false unless there's an error severity
183
+ const hasErrors = result.conflicts.some(c => c.severity === 'error')
184
+ expect(result.hasConflicts).toBe(hasErrors)
185
+ })
186
+ })
187
+
188
+ describe('PreflightPipeline', () => {
189
+ test('full pipeline produces valid result', async () => {
190
+ const pipeline = new PreflightPipeline(mockContract, mockLock)
191
+ const result = await pipeline.run('fix the verifyToken function')
192
+
193
+ expect(result.intents.length).toBeGreaterThan(0)
194
+ expect(result.conflicts).toBeDefined()
195
+ expect(result.conflicts.conflicts).toBeInstanceOf(Array)
196
+ expect(result.suggestions).toBeInstanceOf(Array)
197
+ expect(result.suggestions.length).toBeGreaterThan(0)
198
+ expect(typeof result.approved).toBe('boolean')
199
+ })
200
+
201
+ test('suggestions include affected files', async () => {
202
+ const pipeline = new PreflightPipeline(mockContract, mockLock)
203
+ const result = await pipeline.run('modify verifyToken')
204
+
205
+ const suggestion = result.suggestions.find(s =>
206
+ s.affectedFiles.some(f => f.includes('verify'))
207
+ )
208
+ expect(suggestion).toBeDefined()
209
+ })
210
+ })
@@ -1,5 +1,5 @@
1
- import { expect, test } from "bun:test";
2
-
3
- test("smoke test - intent-engine", () => {
4
- expect(true).toBe(true);
5
- });
1
+ import { expect, test } from "bun:test";
2
+
3
+ test("smoke test - intent-engine", () => {
4
+ expect(true).toBe(true);
5
+ });
package/tsconfig.json CHANGED
@@ -1,15 +1,15 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "dist",
5
- "rootDir": "src"
6
- },
7
- "include": [
8
- "src/**/*"
9
- ],
10
- "exclude": [
11
- "node_modules",
12
- "dist",
13
- "tests"
14
- ]
15
- }
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": [
8
+ "src/**/*"
9
+ ],
10
+ "exclude": [
11
+ "node_modules",
12
+ "dist",
13
+ "tests"
14
+ ]
15
+ }