@vibescope/mcp-server 0.0.1 → 0.2.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 (173) hide show
  1. package/README.md +113 -98
  2. package/dist/api-client.d.ts +1169 -0
  3. package/dist/api-client.js +713 -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 +108 -477
  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 +113 -828
  16. package/dist/handlers/discovery.d.ts +3 -0
  17. package/dist/handlers/discovery.js +26 -627
  18. package/dist/handlers/fallback.d.ts +2 -0
  19. package/dist/handlers/fallback.js +56 -142
  20. package/dist/handlers/findings.d.ts +8 -1
  21. package/dist/handlers/findings.js +65 -68
  22. package/dist/handlers/git-issues.d.ts +9 -13
  23. package/dist/handlers/git-issues.js +80 -225
  24. package/dist/handlers/ideas.d.ts +3 -0
  25. package/dist/handlers/ideas.js +53 -134
  26. package/dist/handlers/index.d.ts +2 -0
  27. package/dist/handlers/index.js +6 -0
  28. package/dist/handlers/milestones.d.ts +2 -0
  29. package/dist/handlers/milestones.js +51 -98
  30. package/dist/handlers/organizations.js +79 -275
  31. package/dist/handlers/progress.d.ts +2 -0
  32. package/dist/handlers/progress.js +25 -123
  33. package/dist/handlers/project.js +42 -221
  34. package/dist/handlers/requests.d.ts +2 -0
  35. package/dist/handlers/requests.js +23 -83
  36. package/dist/handlers/session.js +119 -590
  37. package/dist/handlers/sprints.d.ts +32 -0
  38. package/dist/handlers/sprints.js +275 -0
  39. package/dist/handlers/tasks.d.ts +7 -10
  40. package/dist/handlers/tasks.js +245 -894
  41. package/dist/handlers/tool-docs.d.ts +9 -0
  42. package/dist/handlers/tool-docs.js +904 -0
  43. package/dist/handlers/types.d.ts +11 -3
  44. package/dist/handlers/validation.d.ts +1 -1
  45. package/dist/handlers/validation.js +38 -153
  46. package/dist/index.js +493 -162
  47. package/dist/knowledge.js +106 -9
  48. package/dist/tools.js +34 -4
  49. package/dist/validators.d.ts +21 -0
  50. package/dist/validators.js +91 -0
  51. package/package.json +2 -3
  52. package/src/api-client.ts +1822 -0
  53. package/src/cli.test.ts +128 -302
  54. package/src/cli.ts +41 -285
  55. package/src/handlers/__test-setup__.ts +215 -0
  56. package/src/handlers/__test-utils__.ts +4 -134
  57. package/src/handlers/blockers.test.ts +114 -124
  58. package/src/handlers/blockers.ts +68 -70
  59. package/src/handlers/bodies-of-work.test.ts +236 -831
  60. package/src/handlers/bodies-of-work.ts +210 -525
  61. package/src/handlers/cost.test.ts +149 -113
  62. package/src/handlers/cost.ts +44 -132
  63. package/src/handlers/decisions.test.ts +111 -209
  64. package/src/handlers/decisions.ts +35 -27
  65. package/src/handlers/deployment.test.ts +193 -239
  66. package/src/handlers/deployment.ts +143 -896
  67. package/src/handlers/discovery.test.ts +20 -67
  68. package/src/handlers/discovery.ts +29 -714
  69. package/src/handlers/fallback.test.ts +206 -361
  70. package/src/handlers/fallback.ts +81 -156
  71. package/src/handlers/findings.test.ts +229 -320
  72. package/src/handlers/findings.ts +76 -64
  73. package/src/handlers/git-issues.test.ts +623 -0
  74. package/src/handlers/git-issues.ts +174 -0
  75. package/src/handlers/ideas.test.ts +229 -343
  76. package/src/handlers/ideas.ts +69 -143
  77. package/src/handlers/index.ts +6 -0
  78. package/src/handlers/milestones.test.ts +167 -281
  79. package/src/handlers/milestones.ts +54 -93
  80. package/src/handlers/organizations.test.ts +275 -467
  81. package/src/handlers/organizations.ts +84 -294
  82. package/src/handlers/progress.test.ts +112 -218
  83. package/src/handlers/progress.ts +29 -142
  84. package/src/handlers/project.test.ts +203 -226
  85. package/src/handlers/project.ts +48 -238
  86. package/src/handlers/requests.test.ts +74 -342
  87. package/src/handlers/requests.ts +25 -83
  88. package/src/handlers/session.test.ts +276 -206
  89. package/src/handlers/session.ts +136 -662
  90. package/src/handlers/sprints.test.ts +711 -0
  91. package/src/handlers/sprints.ts +510 -0
  92. package/src/handlers/tasks.test.ts +669 -353
  93. package/src/handlers/tasks.ts +263 -1015
  94. package/src/handlers/tool-docs.ts +1024 -0
  95. package/src/handlers/types.ts +12 -4
  96. package/src/handlers/validation.test.ts +237 -568
  97. package/src/handlers/validation.ts +43 -167
  98. package/src/index.ts +493 -186
  99. package/src/tools.ts +2532 -0
  100. package/src/validators.test.ts +223 -223
  101. package/src/validators.ts +127 -0
  102. package/tsconfig.json +1 -1
  103. package/vitest.config.ts +14 -13
  104. package/dist/cli.test.d.ts +0 -1
  105. package/dist/cli.test.js +0 -367
  106. package/dist/handlers/__test-utils__.d.ts +0 -72
  107. package/dist/handlers/__test-utils__.js +0 -176
  108. package/dist/handlers/checkouts.d.ts +0 -37
  109. package/dist/handlers/checkouts.js +0 -377
  110. package/dist/handlers/knowledge-query.d.ts +0 -22
  111. package/dist/handlers/knowledge-query.js +0 -253
  112. package/dist/handlers/knowledge.d.ts +0 -12
  113. package/dist/handlers/knowledge.js +0 -108
  114. package/dist/handlers/roles.d.ts +0 -30
  115. package/dist/handlers/roles.js +0 -281
  116. package/dist/handlers/tasks.test.d.ts +0 -1
  117. package/dist/handlers/tasks.test.js +0 -431
  118. package/dist/utils.test.d.ts +0 -1
  119. package/dist/utils.test.js +0 -532
  120. package/dist/validators.test.d.ts +0 -1
  121. package/dist/validators.test.js +0 -176
  122. package/src/knowledge.ts +0 -132
  123. package/src/tmpclaude-0078-cwd +0 -1
  124. package/src/tmpclaude-0ee1-cwd +0 -1
  125. package/src/tmpclaude-2dd5-cwd +0 -1
  126. package/src/tmpclaude-344c-cwd +0 -1
  127. package/src/tmpclaude-3860-cwd +0 -1
  128. package/src/tmpclaude-4b63-cwd +0 -1
  129. package/src/tmpclaude-5c73-cwd +0 -1
  130. package/src/tmpclaude-5ee3-cwd +0 -1
  131. package/src/tmpclaude-6795-cwd +0 -1
  132. package/src/tmpclaude-709e-cwd +0 -1
  133. package/src/tmpclaude-9839-cwd +0 -1
  134. package/src/tmpclaude-d829-cwd +0 -1
  135. package/src/tmpclaude-e072-cwd +0 -1
  136. package/src/tmpclaude-f6ee-cwd +0 -1
  137. package/tmpclaude-0439-cwd +0 -1
  138. package/tmpclaude-132f-cwd +0 -1
  139. package/tmpclaude-15bb-cwd +0 -1
  140. package/tmpclaude-165a-cwd +0 -1
  141. package/tmpclaude-1ba9-cwd +0 -1
  142. package/tmpclaude-21a3-cwd +0 -1
  143. package/tmpclaude-2a38-cwd +0 -1
  144. package/tmpclaude-2adf-cwd +0 -1
  145. package/tmpclaude-2f56-cwd +0 -1
  146. package/tmpclaude-3626-cwd +0 -1
  147. package/tmpclaude-3727-cwd +0 -1
  148. package/tmpclaude-40bc-cwd +0 -1
  149. package/tmpclaude-436f-cwd +0 -1
  150. package/tmpclaude-4783-cwd +0 -1
  151. package/tmpclaude-4b6d-cwd +0 -1
  152. package/tmpclaude-4ba4-cwd +0 -1
  153. package/tmpclaude-51e6-cwd +0 -1
  154. package/tmpclaude-5ecf-cwd +0 -1
  155. package/tmpclaude-6f97-cwd +0 -1
  156. package/tmpclaude-7fb2-cwd +0 -1
  157. package/tmpclaude-825c-cwd +0 -1
  158. package/tmpclaude-8baf-cwd +0 -1
  159. package/tmpclaude-8d9f-cwd +0 -1
  160. package/tmpclaude-975c-cwd +0 -1
  161. package/tmpclaude-9983-cwd +0 -1
  162. package/tmpclaude-a045-cwd +0 -1
  163. package/tmpclaude-ac4a-cwd +0 -1
  164. package/tmpclaude-b593-cwd +0 -1
  165. package/tmpclaude-b891-cwd +0 -1
  166. package/tmpclaude-c032-cwd +0 -1
  167. package/tmpclaude-cf43-cwd +0 -1
  168. package/tmpclaude-d040-cwd +0 -1
  169. package/tmpclaude-dcdd-cwd +0 -1
  170. package/tmpclaude-dcee-cwd +0 -1
  171. package/tmpclaude-e16b-cwd +0 -1
  172. package/tmpclaude-ecd2-cwd +0 -1
  173. 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;