@vibescope/mcp-server 0.0.1 → 0.1.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.
Files changed (170) hide show
  1. package/README.md +113 -98
  2. package/dist/api-client.d.ts +1114 -0
  3. package/dist/api-client.js +698 -0
  4. package/dist/cli.d.ts +1 -6
  5. package/dist/cli.js +39 -240
  6. package/dist/config/tool-categories.d.ts +31 -0
  7. package/dist/config/tool-categories.js +253 -0
  8. package/dist/handlers/blockers.js +57 -58
  9. package/dist/handlers/bodies-of-work.d.ts +2 -0
  10. package/dist/handlers/bodies-of-work.js +106 -476
  11. package/dist/handlers/cost.d.ts +1 -0
  12. package/dist/handlers/cost.js +35 -113
  13. package/dist/handlers/decisions.d.ts +2 -0
  14. package/dist/handlers/decisions.js +28 -27
  15. package/dist/handlers/deployment.js +112 -828
  16. package/dist/handlers/discovery.js +31 -0
  17. package/dist/handlers/fallback.d.ts +2 -0
  18. package/dist/handlers/fallback.js +39 -134
  19. package/dist/handlers/findings.js +43 -67
  20. package/dist/handlers/git-issues.d.ts +9 -13
  21. package/dist/handlers/git-issues.js +80 -225
  22. package/dist/handlers/ideas.d.ts +3 -0
  23. package/dist/handlers/ideas.js +53 -134
  24. package/dist/handlers/index.d.ts +2 -0
  25. package/dist/handlers/index.js +6 -0
  26. package/dist/handlers/milestones.d.ts +2 -0
  27. package/dist/handlers/milestones.js +51 -98
  28. package/dist/handlers/organizations.js +79 -275
  29. package/dist/handlers/progress.d.ts +2 -0
  30. package/dist/handlers/progress.js +25 -123
  31. package/dist/handlers/project.js +42 -221
  32. package/dist/handlers/requests.d.ts +2 -0
  33. package/dist/handlers/requests.js +23 -83
  34. package/dist/handlers/session.js +99 -585
  35. package/dist/handlers/sprints.d.ts +32 -0
  36. package/dist/handlers/sprints.js +274 -0
  37. package/dist/handlers/tasks.d.ts +7 -10
  38. package/dist/handlers/tasks.js +230 -900
  39. package/dist/handlers/tool-docs.d.ts +8 -0
  40. package/dist/handlers/tool-docs.js +657 -0
  41. package/dist/handlers/types.d.ts +11 -3
  42. package/dist/handlers/validation.d.ts +1 -1
  43. package/dist/handlers/validation.js +26 -153
  44. package/dist/index.js +473 -160
  45. package/dist/knowledge.js +106 -9
  46. package/dist/tools.js +4 -0
  47. package/dist/validators.d.ts +21 -0
  48. package/dist/validators.js +91 -0
  49. package/package.json +2 -3
  50. package/src/api-client.ts +1752 -0
  51. package/src/cli.test.ts +128 -302
  52. package/src/cli.ts +41 -285
  53. package/src/handlers/__test-setup__.ts +210 -0
  54. package/src/handlers/__test-utils__.ts +4 -134
  55. package/src/handlers/blockers.test.ts +114 -124
  56. package/src/handlers/blockers.ts +68 -70
  57. package/src/handlers/bodies-of-work.test.ts +236 -831
  58. package/src/handlers/bodies-of-work.ts +194 -525
  59. package/src/handlers/cost.test.ts +149 -113
  60. package/src/handlers/cost.ts +44 -132
  61. package/src/handlers/decisions.test.ts +111 -209
  62. package/src/handlers/decisions.ts +35 -27
  63. package/src/handlers/deployment.test.ts +193 -239
  64. package/src/handlers/deployment.ts +140 -895
  65. package/src/handlers/discovery.test.ts +20 -67
  66. package/src/handlers/discovery.ts +32 -0
  67. package/src/handlers/fallback.test.ts +128 -361
  68. package/src/handlers/fallback.ts +62 -148
  69. package/src/handlers/findings.test.ts +127 -345
  70. package/src/handlers/findings.ts +49 -66
  71. package/src/handlers/git-issues.test.ts +623 -0
  72. package/src/handlers/git-issues.ts +174 -0
  73. package/src/handlers/ideas.test.ts +229 -343
  74. package/src/handlers/ideas.ts +69 -143
  75. package/src/handlers/index.ts +6 -0
  76. package/src/handlers/milestones.test.ts +167 -281
  77. package/src/handlers/milestones.ts +54 -93
  78. package/src/handlers/organizations.test.ts +275 -467
  79. package/src/handlers/organizations.ts +84 -294
  80. package/src/handlers/progress.test.ts +112 -218
  81. package/src/handlers/progress.ts +29 -142
  82. package/src/handlers/project.test.ts +203 -226
  83. package/src/handlers/project.ts +48 -238
  84. package/src/handlers/requests.test.ts +74 -342
  85. package/src/handlers/requests.ts +25 -83
  86. package/src/handlers/session.test.ts +241 -206
  87. package/src/handlers/session.ts +110 -657
  88. package/src/handlers/sprints.test.ts +711 -0
  89. package/src/handlers/sprints.ts +497 -0
  90. package/src/handlers/tasks.test.ts +608 -353
  91. package/src/handlers/tasks.ts +248 -1025
  92. package/src/handlers/types.ts +12 -4
  93. package/src/handlers/validation.test.ts +189 -572
  94. package/src/handlers/validation.ts +29 -166
  95. package/src/index.ts +473 -184
  96. package/src/knowledge.ts +107 -9
  97. package/src/tools.ts +2506 -0
  98. package/src/validators.test.ts +223 -223
  99. package/src/validators.ts +127 -0
  100. package/tsconfig.json +1 -1
  101. package/vitest.config.ts +14 -13
  102. package/dist/cli.test.d.ts +0 -1
  103. package/dist/cli.test.js +0 -367
  104. package/dist/handlers/__test-utils__.d.ts +0 -72
  105. package/dist/handlers/__test-utils__.js +0 -176
  106. package/dist/handlers/checkouts.d.ts +0 -37
  107. package/dist/handlers/checkouts.js +0 -377
  108. package/dist/handlers/knowledge-query.d.ts +0 -22
  109. package/dist/handlers/knowledge-query.js +0 -253
  110. package/dist/handlers/knowledge.d.ts +0 -12
  111. package/dist/handlers/knowledge.js +0 -108
  112. package/dist/handlers/roles.d.ts +0 -30
  113. package/dist/handlers/roles.js +0 -281
  114. package/dist/handlers/tasks.test.d.ts +0 -1
  115. package/dist/handlers/tasks.test.js +0 -431
  116. package/dist/utils.test.d.ts +0 -1
  117. package/dist/utils.test.js +0 -532
  118. package/dist/validators.test.d.ts +0 -1
  119. package/dist/validators.test.js +0 -176
  120. package/src/tmpclaude-0078-cwd +0 -1
  121. package/src/tmpclaude-0ee1-cwd +0 -1
  122. package/src/tmpclaude-2dd5-cwd +0 -1
  123. package/src/tmpclaude-344c-cwd +0 -1
  124. package/src/tmpclaude-3860-cwd +0 -1
  125. package/src/tmpclaude-4b63-cwd +0 -1
  126. package/src/tmpclaude-5c73-cwd +0 -1
  127. package/src/tmpclaude-5ee3-cwd +0 -1
  128. package/src/tmpclaude-6795-cwd +0 -1
  129. package/src/tmpclaude-709e-cwd +0 -1
  130. package/src/tmpclaude-9839-cwd +0 -1
  131. package/src/tmpclaude-d829-cwd +0 -1
  132. package/src/tmpclaude-e072-cwd +0 -1
  133. package/src/tmpclaude-f6ee-cwd +0 -1
  134. package/tmpclaude-0439-cwd +0 -1
  135. package/tmpclaude-132f-cwd +0 -1
  136. package/tmpclaude-15bb-cwd +0 -1
  137. package/tmpclaude-165a-cwd +0 -1
  138. package/tmpclaude-1ba9-cwd +0 -1
  139. package/tmpclaude-21a3-cwd +0 -1
  140. package/tmpclaude-2a38-cwd +0 -1
  141. package/tmpclaude-2adf-cwd +0 -1
  142. package/tmpclaude-2f56-cwd +0 -1
  143. package/tmpclaude-3626-cwd +0 -1
  144. package/tmpclaude-3727-cwd +0 -1
  145. package/tmpclaude-40bc-cwd +0 -1
  146. package/tmpclaude-436f-cwd +0 -1
  147. package/tmpclaude-4783-cwd +0 -1
  148. package/tmpclaude-4b6d-cwd +0 -1
  149. package/tmpclaude-4ba4-cwd +0 -1
  150. package/tmpclaude-51e6-cwd +0 -1
  151. package/tmpclaude-5ecf-cwd +0 -1
  152. package/tmpclaude-6f97-cwd +0 -1
  153. package/tmpclaude-7fb2-cwd +0 -1
  154. package/tmpclaude-825c-cwd +0 -1
  155. package/tmpclaude-8baf-cwd +0 -1
  156. package/tmpclaude-8d9f-cwd +0 -1
  157. package/tmpclaude-975c-cwd +0 -1
  158. package/tmpclaude-9983-cwd +0 -1
  159. package/tmpclaude-a045-cwd +0 -1
  160. package/tmpclaude-ac4a-cwd +0 -1
  161. package/tmpclaude-b593-cwd +0 -1
  162. package/tmpclaude-b891-cwd +0 -1
  163. package/tmpclaude-c032-cwd +0 -1
  164. package/tmpclaude-cf43-cwd +0 -1
  165. package/tmpclaude-d040-cwd +0 -1
  166. package/tmpclaude-dcdd-cwd +0 -1
  167. package/tmpclaude-dcee-cwd +0 -1
  168. package/tmpclaude-e16b-cwd +0 -1
  169. package/tmpclaude-ecd2-cwd +0 -1
  170. package/tmpclaude-f48d-cwd +0 -1
package/dist/cli.test.js DELETED
@@ -1,367 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { createHash } from 'crypto';
3
- // Mock child_process before importing cli module
4
- vi.mock('child_process', () => ({
5
- execSync: vi.fn(),
6
- }));
7
- // Mock @supabase/supabase-js
8
- vi.mock('@supabase/supabase-js', () => ({
9
- createClient: vi.fn(),
10
- }));
11
- import { execSync } from 'child_process';
12
- import { hashApiKey, detectGitUrl, validateApiKey, } from './cli.js';
13
- describe('CLI verification logic', () => {
14
- beforeEach(() => {
15
- vi.clearAllMocks();
16
- });
17
- describe('hashApiKey', () => {
18
- it('should return SHA256 hash of the key', () => {
19
- const key = 'test-api-key';
20
- const expected = createHash('sha256').update(key).digest('hex');
21
- expect(hashApiKey(key)).toBe(expected);
22
- });
23
- it('should return different hashes for different keys', () => {
24
- const hash1 = hashApiKey('key1');
25
- const hash2 = hashApiKey('key2');
26
- expect(hash1).not.toBe(hash2);
27
- });
28
- it('should return same hash for same key', () => {
29
- const hash1 = hashApiKey('same-key');
30
- const hash2 = hashApiKey('same-key');
31
- expect(hash1).toBe(hash2);
32
- });
33
- it('should return 64-character hex string', () => {
34
- const hash = hashApiKey('any-key');
35
- expect(hash).toMatch(/^[a-f0-9]{64}$/);
36
- });
37
- });
38
- describe('detectGitUrl', () => {
39
- it('should return normalized git URL when git command succeeds', () => {
40
- vi.mocked(execSync).mockReturnValue('git@github.com:user/repo.git\n');
41
- const result = detectGitUrl();
42
- expect(result).toBe('https://github.com/user/repo');
43
- });
44
- it('should return null when git command fails', () => {
45
- vi.mocked(execSync).mockImplementation(() => {
46
- throw new Error('Not a git repository');
47
- });
48
- const result = detectGitUrl();
49
- expect(result).toBeNull();
50
- });
51
- it('should handle HTTPS URLs', () => {
52
- vi.mocked(execSync).mockReturnValue('https://github.com/user/repo.git\n');
53
- const result = detectGitUrl();
54
- expect(result).toBe('https://github.com/user/repo');
55
- });
56
- it('should call execSync with correct options', () => {
57
- vi.mocked(execSync).mockReturnValue('https://github.com/user/repo.git');
58
- detectGitUrl();
59
- expect(execSync).toHaveBeenCalledWith('git config --get remote.origin.url', {
60
- encoding: 'utf8',
61
- timeout: 5000,
62
- stdio: ['pipe', 'pipe', 'pipe'],
63
- });
64
- });
65
- });
66
- describe('validateApiKey', () => {
67
- it('should return auth context for valid API key', async () => {
68
- const mockSupabase = {
69
- from: vi.fn().mockReturnThis(),
70
- select: vi.fn().mockReturnThis(),
71
- eq: vi.fn().mockReturnThis(),
72
- single: vi.fn().mockResolvedValue({
73
- data: { id: 'key-id-123', user_id: 'user-id-456' },
74
- error: null,
75
- }),
76
- };
77
- const result = await validateApiKey(mockSupabase, 'test-api-key');
78
- expect(result).toEqual({
79
- userId: 'user-id-456',
80
- apiKeyId: 'key-id-123',
81
- });
82
- });
83
- it('should return null for invalid API key', async () => {
84
- const mockSupabase = {
85
- from: vi.fn().mockReturnThis(),
86
- select: vi.fn().mockReturnThis(),
87
- eq: vi.fn().mockReturnThis(),
88
- single: vi.fn().mockResolvedValue({
89
- data: null,
90
- error: { message: 'Not found' },
91
- }),
92
- };
93
- const result = await validateApiKey(mockSupabase, 'invalid-key');
94
- expect(result).toBeNull();
95
- });
96
- it('should query api_keys table with hashed key', async () => {
97
- const mockFrom = vi.fn().mockReturnThis();
98
- const mockSelect = vi.fn().mockReturnThis();
99
- const mockEq = vi.fn().mockReturnThis();
100
- const mockSingle = vi.fn().mockResolvedValue({ data: null, error: { message: 'Not found' } });
101
- const mockSupabase = {
102
- from: mockFrom,
103
- select: mockSelect,
104
- eq: mockEq,
105
- single: mockSingle,
106
- };
107
- await validateApiKey(mockSupabase, 'test-key');
108
- expect(mockFrom).toHaveBeenCalledWith('api_keys');
109
- expect(mockSelect).toHaveBeenCalledWith('id, user_id');
110
- expect(mockEq).toHaveBeenCalledWith('key_hash', hashApiKey('test-key'));
111
- });
112
- it('should return null when data is missing', async () => {
113
- const mockSupabase = {
114
- from: vi.fn().mockReturnThis(),
115
- select: vi.fn().mockReturnThis(),
116
- eq: vi.fn().mockReturnThis(),
117
- single: vi.fn().mockResolvedValue({
118
- data: undefined,
119
- error: null,
120
- }),
121
- };
122
- const result = await validateApiKey(mockSupabase, 'test-key');
123
- expect(result).toBeNull();
124
- });
125
- });
126
- });
127
- describe('verify function integration', () => {
128
- const originalEnv = process.env;
129
- beforeEach(() => {
130
- vi.clearAllMocks();
131
- // Reset process.env for each test
132
- process.env = { ...originalEnv };
133
- });
134
- afterEach(() => {
135
- process.env = originalEnv;
136
- });
137
- // Note: The verify function reads environment variables at module load time,
138
- // so we need to test through dynamic imports or by mocking at a higher level.
139
- // For now, we test the exported functions individually.
140
- describe('environment variable validation', () => {
141
- it('hashApiKey should work without env vars', () => {
142
- // hashApiKey is a pure function that doesn't depend on env vars
143
- expect(hashApiKey('test')).toBeTruthy();
144
- });
145
- it('detectGitUrl should work without env vars', () => {
146
- vi.mocked(execSync).mockReturnValue('https://github.com/user/repo.git');
147
- expect(detectGitUrl()).toBe('https://github.com/user/repo');
148
- });
149
- });
150
- describe('validateApiKey error handling', () => {
151
- it('should handle database errors gracefully', async () => {
152
- const mockSupabase = {
153
- from: vi.fn().mockReturnThis(),
154
- select: vi.fn().mockReturnThis(),
155
- eq: vi.fn().mockReturnThis(),
156
- single: vi.fn().mockRejectedValue(new Error('Database connection failed')),
157
- };
158
- await expect(validateApiKey(mockSupabase, 'test-key')).rejects.toThrow('Database connection failed');
159
- });
160
- it('should handle empty string API key', async () => {
161
- const mockSupabase = {
162
- from: vi.fn().mockReturnThis(),
163
- select: vi.fn().mockReturnThis(),
164
- eq: vi.fn().mockReturnThis(),
165
- single: vi.fn().mockResolvedValue({ data: null, error: null }),
166
- };
167
- const result = await validateApiKey(mockSupabase, '');
168
- expect(result).toBeNull();
169
- });
170
- });
171
- });
172
- describe('VerificationResult types', () => {
173
- it('should have correct status types', () => {
174
- const compliant = {
175
- status: 'compliant',
176
- reason: 'All good',
177
- };
178
- expect(compliant.status).toBe('compliant');
179
- const nonCompliant = {
180
- status: 'non_compliant',
181
- reason: 'Tasks in progress',
182
- continuation_prompt: 'Complete your tasks',
183
- };
184
- expect(nonCompliant.status).toBe('non_compliant');
185
- const error = {
186
- status: 'error',
187
- reason: 'Missing env vars',
188
- };
189
- expect(error.status).toBe('error');
190
- const noSession = {
191
- status: 'no_session',
192
- reason: 'No git URL detected',
193
- };
194
- expect(noSession.status).toBe('no_session');
195
- });
196
- it('should support optional details', () => {
197
- const result = {
198
- status: 'compliant',
199
- reason: 'All good',
200
- details: {
201
- session_started: true,
202
- project_id: 'proj-123',
203
- project_name: 'Test Project',
204
- git_url: 'https://github.com/user/repo',
205
- in_progress_tasks: 0,
206
- tasks_completed_this_session: 5,
207
- progress_logs_this_session: 3,
208
- blockers_logged_this_session: 1,
209
- session_duration_minutes: 45,
210
- },
211
- };
212
- expect(result.details?.session_started).toBe(true);
213
- expect(result.details?.tasks_completed_this_session).toBe(5);
214
- });
215
- });
216
- describe('AuthContext type', () => {
217
- it('should have required fields', () => {
218
- const auth = {
219
- userId: 'user-123',
220
- apiKeyId: 'key-456',
221
- };
222
- expect(auth.userId).toBe('user-123');
223
- expect(auth.apiKeyId).toBe('key-456');
224
- });
225
- });
226
- describe('verify function scenarios', () => {
227
- const originalEnv = { ...process.env };
228
- beforeEach(() => {
229
- vi.clearAllMocks();
230
- // Set up required env vars
231
- process.env.SUPABASE_URL = 'https://test.supabase.co';
232
- process.env.SUPABASE_SERVICE_KEY = 'test-service-key';
233
- process.env.VIBESCOPE_API_KEY = 'test-api-key';
234
- });
235
- afterEach(() => {
236
- process.env = { ...originalEnv };
237
- });
238
- // Helper to create a mock Supabase client with chainable methods
239
- function createMockSupabase(overrides = {}) {
240
- const defaultApiKey = { data: { id: 'key-id', user_id: 'user-id' }, error: null };
241
- const defaultProject = {
242
- data: { id: 'proj-id', name: 'Test Project', git_url: 'https://github.com/test/repo' },
243
- error: null,
244
- };
245
- const defaultSession = {
246
- data: {
247
- id: 'session-id',
248
- created_at: new Date(Date.now() - 10 * 60 * 1000).toISOString(), // 10 mins ago
249
- last_synced_at: new Date().toISOString(),
250
- },
251
- error: null,
252
- };
253
- const defaultTasks = { data: [], error: null };
254
- const defaultCompleted = { count: 1, error: null };
255
- const defaultProgress = { count: 0, error: null };
256
- const defaultBlocker = { count: 0, error: null };
257
- let currentTable = '';
258
- let selectCount = false;
259
- const mock = {
260
- from: vi.fn((table) => {
261
- currentTable = table;
262
- return mock;
263
- }),
264
- select: vi.fn((cols, opts) => {
265
- selectCount = !!opts?.count;
266
- return mock;
267
- }),
268
- eq: vi.fn().mockReturnThis(),
269
- gte: vi.fn().mockReturnThis(),
270
- single: vi.fn(() => {
271
- if (currentTable === 'api_keys') {
272
- return Promise.resolve(overrides.apiKeyResult ?? defaultApiKey);
273
- }
274
- if (currentTable === 'projects') {
275
- return Promise.resolve(overrides.projectResult ?? defaultProject);
276
- }
277
- if (currentTable === 'agent_sessions') {
278
- return Promise.resolve(overrides.sessionResult ?? defaultSession);
279
- }
280
- return Promise.resolve({ data: null, error: null });
281
- }),
282
- then: vi.fn((resolve) => {
283
- if (currentTable === 'tasks') {
284
- if (selectCount) {
285
- return Promise.resolve(overrides.completedResult ?? defaultCompleted).then(resolve);
286
- }
287
- return Promise.resolve(overrides.tasksResult ?? defaultTasks).then(resolve);
288
- }
289
- if (currentTable === 'progress_logs') {
290
- return Promise.resolve(overrides.progressResult ?? defaultProgress).then(resolve);
291
- }
292
- if (currentTable === 'blockers') {
293
- return Promise.resolve(overrides.blockerResult ?? defaultBlocker).then(resolve);
294
- }
295
- return Promise.resolve({ data: null, count: 0 }).then(resolve);
296
- }),
297
- };
298
- return mock;
299
- }
300
- it('should validate API key and return auth context', async () => {
301
- const mockSupabase = createMockSupabase();
302
- const result = await validateApiKey(mockSupabase, 'test-key');
303
- expect(result).toEqual({
304
- userId: 'user-id',
305
- apiKeyId: 'key-id',
306
- });
307
- });
308
- it('should return null for invalid API key', async () => {
309
- const mockSupabase = createMockSupabase({
310
- apiKeyResult: { data: null, error: { message: 'Not found' } },
311
- });
312
- const result = await validateApiKey(mockSupabase, 'invalid-key');
313
- expect(result).toBeNull();
314
- });
315
- it('should handle database query errors', async () => {
316
- const mockSupabase = {
317
- from: vi.fn().mockReturnThis(),
318
- select: vi.fn().mockReturnThis(),
319
- eq: vi.fn().mockReturnThis(),
320
- single: vi.fn().mockRejectedValue(new Error('Connection timeout')),
321
- };
322
- await expect(validateApiKey(mockSupabase, 'key')).rejects.toThrow('Connection timeout');
323
- });
324
- });
325
- describe('detectGitUrl edge cases', () => {
326
- beforeEach(() => {
327
- vi.clearAllMocks();
328
- });
329
- it('should trim whitespace from git URL', () => {
330
- vi.mocked(execSync).mockReturnValue(' https://github.com/user/repo.git \n');
331
- const result = detectGitUrl();
332
- expect(result).toBe('https://github.com/user/repo');
333
- });
334
- it('should handle GitLab SSH URLs', () => {
335
- vi.mocked(execSync).mockReturnValue('git@gitlab.com:user/repo.git');
336
- const result = detectGitUrl();
337
- expect(result).toBe('https://gitlab.com/user/repo');
338
- });
339
- it('should handle Azure DevOps SSH URLs (not normalized)', () => {
340
- // Azure DevOps URLs are not normalized by the current implementation
341
- // The URL is trimmed but SSH format is preserved
342
- vi.mocked(execSync).mockReturnValue('git@ssh.dev.azure.com:v3/org/project/repo');
343
- const result = detectGitUrl();
344
- expect(result).toBe('git@ssh.dev.azure.com:v3/org/project/repo');
345
- });
346
- it('should handle Bitbucket SSH URLs', () => {
347
- vi.mocked(execSync).mockReturnValue('git@bitbucket.org:user/repo.git');
348
- const result = detectGitUrl();
349
- expect(result).toBe('https://bitbucket.org/user/repo');
350
- });
351
- it('should return null on timeout', () => {
352
- vi.mocked(execSync).mockImplementation(() => {
353
- const error = new Error('Command timed out');
354
- error.code = 'ETIMEDOUT';
355
- throw error;
356
- });
357
- const result = detectGitUrl();
358
- expect(result).toBeNull();
359
- });
360
- it('should return null when not in a git repo', () => {
361
- vi.mocked(execSync).mockImplementation(() => {
362
- throw new Error('fatal: not a git repository');
363
- });
364
- const result = detectGitUrl();
365
- expect(result).toBeNull();
366
- });
367
- });
@@ -1,72 +0,0 @@
1
- /**
2
- * Shared Test Utilities
3
- *
4
- * Common mock factories and test helpers used across handler tests.
5
- * This eliminates ~85 lines of duplicate code per test file.
6
- */
7
- import type { SupabaseClient } from '@supabase/supabase-js';
8
- import type { HandlerContext } from './types.js';
9
- export interface MockSupabaseOverrides {
10
- selectResult?: {
11
- data: unknown;
12
- error: unknown;
13
- };
14
- insertResult?: {
15
- data: unknown;
16
- error: unknown;
17
- };
18
- updateResult?: {
19
- data: unknown;
20
- error: unknown;
21
- };
22
- deleteResult?: {
23
- data: unknown;
24
- error: unknown;
25
- };
26
- sessionsResult?: {
27
- data: unknown;
28
- error: unknown;
29
- };
30
- }
31
- /**
32
- * Create a mock Supabase client for testing.
33
- *
34
- * The mock tracks which operation is being performed and returns
35
- * the appropriate result from overrides.
36
- *
37
- * @example
38
- * ```ts
39
- * const supabase = createMockSupabase({
40
- * insertResult: { data: { id: 'new-id' }, error: null }
41
- * });
42
- * ```
43
- */
44
- export declare function createMockSupabase(overrides?: MockSupabaseOverrides): SupabaseClient;
45
- export interface MockContextOptions {
46
- sessionId?: string | null;
47
- userId?: string;
48
- apiKeyId?: string;
49
- instanceId?: string;
50
- persona?: string;
51
- }
52
- /**
53
- * Create a mock HandlerContext for testing.
54
- *
55
- * @example
56
- * ```ts
57
- * const ctx = createMockContext(supabase, { sessionId: 'test-session' });
58
- * ```
59
- */
60
- export declare function createMockContext(supabase: SupabaseClient, options?: MockContextOptions): HandlerContext;
61
- /**
62
- * Generate a valid UUID for testing.
63
- */
64
- export declare function testUUID(): string;
65
- /**
66
- * Generate a random-ish UUID for testing (deterministic based on seed).
67
- */
68
- export declare function testUUIDSeeded(seed: number): string;
69
- /**
70
- * Create a mock timestamp for consistent testing.
71
- */
72
- export declare function testTimestamp(offsetMinutes?: number): string;
@@ -1,176 +0,0 @@
1
- /**
2
- * Shared Test Utilities
3
- *
4
- * Common mock factories and test helpers used across handler tests.
5
- * This eliminates ~85 lines of duplicate code per test file.
6
- */
7
- import { vi } from 'vitest';
8
- /**
9
- * Create a mock Supabase client for testing.
10
- *
11
- * The mock tracks which operation is being performed and returns
12
- * the appropriate result from overrides.
13
- *
14
- * @example
15
- * ```ts
16
- * const supabase = createMockSupabase({
17
- * insertResult: { data: { id: 'new-id' }, error: null }
18
- * });
19
- * ```
20
- */
21
- export function createMockSupabase(overrides = {}) {
22
- const defaultResult = { data: null, error: null };
23
- // Use an object to track state so it persists across all mock function calls
24
- const state = {
25
- currentOperation: 'select',
26
- currentTable: '',
27
- insertThenSelect: false,
28
- updateCalled: false,
29
- };
30
- const mock = {
31
- from: vi.fn((table) => {
32
- state.currentTable = table;
33
- // Reset state for new query chain
34
- state.currentOperation = 'select';
35
- state.insertThenSelect = false;
36
- state.updateCalled = false;
37
- return mock;
38
- }),
39
- select: vi.fn(() => {
40
- if (state.currentOperation === 'insert') {
41
- state.insertThenSelect = true;
42
- }
43
- else if (!state.updateCalled) {
44
- state.currentOperation = 'select';
45
- state.insertThenSelect = false;
46
- }
47
- return mock;
48
- }),
49
- insert: vi.fn(() => {
50
- state.currentOperation = 'insert';
51
- state.insertThenSelect = false;
52
- return mock;
53
- }),
54
- update: vi.fn(() => {
55
- state.currentOperation = 'update';
56
- state.updateCalled = true;
57
- state.insertThenSelect = false;
58
- return mock;
59
- }),
60
- delete: vi.fn(() => {
61
- state.currentOperation = 'delete';
62
- state.insertThenSelect = false;
63
- return mock;
64
- }),
65
- eq: vi.fn().mockReturnThis(),
66
- neq: vi.fn().mockReturnThis(),
67
- in: vi.fn().mockReturnThis(),
68
- is: vi.fn().mockReturnThis(),
69
- not: vi.fn().mockReturnThis(),
70
- or: vi.fn().mockReturnThis(),
71
- gt: vi.fn().mockReturnThis(),
72
- gte: vi.fn().mockReturnThis(),
73
- lte: vi.fn().mockReturnThis(),
74
- lt: vi.fn().mockReturnThis(),
75
- order: vi.fn().mockReturnThis(),
76
- limit: vi.fn().mockReturnThis(),
77
- single: vi.fn(() => {
78
- if (state.currentOperation === 'insert' || state.insertThenSelect) {
79
- return Promise.resolve(overrides.insertResult ?? defaultResult);
80
- }
81
- if (state.updateCalled) {
82
- return Promise.resolve(overrides.updateResult ?? defaultResult);
83
- }
84
- if (state.currentOperation === 'select') {
85
- return Promise.resolve(overrides.selectResult ?? defaultResult);
86
- }
87
- if (state.currentOperation === 'update') {
88
- return Promise.resolve(overrides.updateResult ?? defaultResult);
89
- }
90
- return Promise.resolve(defaultResult);
91
- }),
92
- maybeSingle: vi.fn(() => {
93
- return Promise.resolve(overrides.selectResult ?? defaultResult);
94
- }),
95
- then: vi.fn((resolve) => {
96
- // Handle special table cases
97
- if (state.currentTable === 'agent_sessions' && overrides.sessionsResult) {
98
- return Promise.resolve(overrides.sessionsResult).then(resolve);
99
- }
100
- if (state.currentOperation === 'insert' || state.insertThenSelect) {
101
- return Promise.resolve(overrides.insertResult ?? defaultResult).then(resolve);
102
- }
103
- if (state.updateCalled) {
104
- return Promise.resolve(overrides.updateResult ?? defaultResult).then(resolve);
105
- }
106
- if (state.currentOperation === 'select') {
107
- return Promise.resolve(overrides.selectResult ?? defaultResult).then(resolve);
108
- }
109
- if (state.currentOperation === 'update') {
110
- return Promise.resolve(overrides.updateResult ?? defaultResult).then(resolve);
111
- }
112
- if (state.currentOperation === 'delete') {
113
- return Promise.resolve(overrides.deleteResult ?? defaultResult).then(resolve);
114
- }
115
- return Promise.resolve(defaultResult).then(resolve);
116
- }),
117
- };
118
- return mock;
119
- }
120
- /**
121
- * Create a mock HandlerContext for testing.
122
- *
123
- * @example
124
- * ```ts
125
- * const ctx = createMockContext(supabase, { sessionId: 'test-session' });
126
- * ```
127
- */
128
- export function createMockContext(supabase, options = {}) {
129
- const defaultTokenUsage = {
130
- callCount: 5,
131
- totalTokens: 2500,
132
- byTool: {},
133
- byModel: {},
134
- currentModel: null,
135
- };
136
- const sessionId = 'sessionId' in options ? (options.sessionId ?? null) : 'session-123';
137
- return {
138
- supabase,
139
- auth: {
140
- userId: options.userId ?? 'user-123',
141
- apiKeyId: options.apiKeyId ?? 'api-key-123',
142
- scope: 'personal',
143
- },
144
- session: {
145
- instanceId: options.instanceId ?? 'instance-abc',
146
- currentSessionId: sessionId,
147
- currentPersona: options.persona ?? 'Wave',
148
- tokenUsage: defaultTokenUsage,
149
- },
150
- updateSession: vi.fn(),
151
- };
152
- }
153
- // ============================================================================
154
- // Test Data Generators
155
- // ============================================================================
156
- /**
157
- * Generate a valid UUID for testing.
158
- */
159
- export function testUUID() {
160
- return '123e4567-e89b-12d3-a456-426614174000';
161
- }
162
- /**
163
- * Generate a random-ish UUID for testing (deterministic based on seed).
164
- */
165
- export function testUUIDSeeded(seed) {
166
- const hex = seed.toString(16).padStart(8, '0');
167
- return `${hex}-e89b-12d3-a456-426614174000`;
168
- }
169
- /**
170
- * Create a mock timestamp for consistent testing.
171
- */
172
- export function testTimestamp(offsetMinutes = 0) {
173
- const date = new Date('2025-01-14T12:00:00Z');
174
- date.setMinutes(date.getMinutes() + offsetMinutes);
175
- return date.toISOString();
176
- }
@@ -1,37 +0,0 @@
1
- /**
2
- * File Checkout Handlers
3
- *
4
- * Handles file checkout/checkin for multi-agent coordination:
5
- * - checkout_file: Reserve a file for editing
6
- * - checkin_file: Release a file after editing
7
- * - get_file_checkouts: List active and recent checkouts
8
- * - abandon_checkout: Force-release a checkout
9
- */
10
- import type { Handler, HandlerRegistry } from './types.js';
11
- /**
12
- * Checkout a file for editing
13
- * Prevents other agents from editing the same file
14
- */
15
- export declare const checkoutFile: Handler;
16
- /**
17
- * Checkin a file after editing
18
- * Releases the file for other agents to edit
19
- */
20
- export declare const checkinFile: Handler;
21
- /**
22
- * Get file checkouts for a project
23
- */
24
- export declare const getFileCheckouts: Handler;
25
- /**
26
- * Abandon a checkout (force release)
27
- * Use when the original agent session died or is stuck
28
- */
29
- export declare const abandonCheckout: Handler;
30
- /**
31
- * Check if a file is available for checkout
32
- */
33
- export declare const isFileAvailable: Handler;
34
- /**
35
- * Checkout handlers registry
36
- */
37
- export declare const checkoutHandlers: HandlerRegistry;