@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/src/cli.ts CHANGED
@@ -10,20 +10,12 @@
10
10
  * 2 = Error (allow exit with warning)
11
11
  */
12
12
 
13
- import { createClient } from '@supabase/supabase-js';
14
- import { createHash } from 'crypto';
15
13
  import { execSync } from 'child_process';
16
- import { normalizeGitUrl } from './utils.js';
17
14
 
18
15
  // ============================================================================
19
16
  // Types
20
17
  // ============================================================================
21
18
 
22
- export interface AuthContext {
23
- userId: string;
24
- apiKeyId: string;
25
- }
26
-
27
19
  export interface VerificationResult {
28
20
  status: 'compliant' | 'non_compliant' | 'no_session' | 'error';
29
21
  reason: string;
@@ -41,24 +33,34 @@ export interface VerificationResult {
41
33
  };
42
34
  }
43
35
 
44
- interface TaskInfo {
45
- id: string;
46
- title: string;
47
- progress_percentage: number;
48
- }
49
-
50
36
  // ============================================================================
51
- // Configuration
37
+ // Configuration (read at runtime for testability)
52
38
  // ============================================================================
53
39
 
54
- const SUPABASE_URL = process.env.SUPABASE_URL || process.env.PUBLIC_SUPABASE_URL;
55
- const SUPABASE_SERVICE_KEY = process.env.SUPABASE_SERVICE_KEY;
56
- const API_KEY = process.env.VIBESCOPE_API_KEY;
40
+ function getApiKey(): string | undefined {
41
+ return process.env.VIBESCOPE_API_KEY;
42
+ }
43
+
44
+ function getApiUrl(): string {
45
+ return process.env.VIBESCOPE_API_URL || 'https://vibescope.dev';
46
+ }
57
47
 
58
48
  // ============================================================================
59
49
  // Git URL Detection
60
50
  // ============================================================================
61
51
 
52
+ export function normalizeGitUrl(url: string): string {
53
+ // Remove .git suffix
54
+ let normalized = url.replace(/\.git$/, '');
55
+ // Convert SSH to HTTPS format
56
+ if (normalized.startsWith('git@')) {
57
+ normalized = normalized
58
+ .replace(/^git@/, 'https://')
59
+ .replace(/:([^/])/, '/$1');
60
+ }
61
+ return normalized;
62
+ }
63
+
62
64
  export function detectGitUrl(): string | null {
63
65
  try {
64
66
  const url = execSync('git config --get remote.origin.url', {
@@ -67,47 +69,12 @@ export function detectGitUrl(): string | null {
67
69
  stdio: ['pipe', 'pipe', 'pipe'],
68
70
  }).trim();
69
71
 
70
- // Normalize: remove .git suffix, convert SSH to HTTPS format
71
72
  return normalizeGitUrl(url);
72
73
  } catch {
73
74
  return null;
74
75
  }
75
76
  }
76
77
 
77
- // ============================================================================
78
- // Authentication (reused from index.ts)
79
- // ============================================================================
80
-
81
- export function hashApiKey(key: string): string {
82
- return createHash('sha256').update(key).digest('hex');
83
- }
84
-
85
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
- export async function validateApiKey(
87
- supabase: any,
88
- apiKey: string
89
- ): Promise<AuthContext | null> {
90
- const keyHash = hashApiKey(apiKey);
91
-
92
- const { data, error } = await supabase
93
- .from('api_keys')
94
- .select('id, user_id')
95
- .eq('key_hash', keyHash)
96
- .single();
97
-
98
- if (error || !data) {
99
- return null;
100
- }
101
-
102
- // Cast to expected shape since we're using untyped client
103
- const row = data as { id: string; user_id: string };
104
-
105
- return {
106
- userId: row.user_id,
107
- apiKeyId: row.id,
108
- };
109
- }
110
-
111
78
  // ============================================================================
112
79
  // Verification Logic
113
80
  // ============================================================================
@@ -116,251 +83,41 @@ export async function verify(
116
83
  gitUrl?: string,
117
84
  projectId?: string
118
85
  ): Promise<VerificationResult> {
119
- // Check environment
120
- if (!SUPABASE_URL || !SUPABASE_SERVICE_KEY) {
121
- return {
122
- status: 'error',
123
- reason: 'Missing SUPABASE_URL or SUPABASE_SERVICE_KEY environment variables',
124
- };
125
- }
126
-
127
- if (!API_KEY) {
86
+ // Check environment (read at runtime for testability)
87
+ const apiKey = getApiKey();
88
+ if (!apiKey) {
128
89
  return {
129
90
  status: 'error',
130
91
  reason: 'VIBESCOPE_API_KEY environment variable not set',
131
92
  };
132
93
  }
133
94
 
134
- const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_KEY);
135
-
136
- // Validate API key
137
- const auth = await validateApiKey(supabase, API_KEY);
138
- if (!auth) {
139
- return {
140
- status: 'error',
141
- reason: 'Invalid VIBESCOPE_API_KEY',
142
- };
143
- }
144
-
145
95
  // Auto-detect git URL if not provided
146
96
  if (!gitUrl && !projectId) {
147
97
  gitUrl = detectGitUrl() || undefined;
148
98
  }
149
99
 
150
- // Find project
151
- let projectQuery = supabase
152
- .from('projects')
153
- .select('id, name, git_url')
154
- .eq('user_id', auth.userId);
155
-
156
- if (projectId) {
157
- projectQuery = projectQuery.eq('id', projectId);
158
- } else if (gitUrl) {
159
- projectQuery = projectQuery.eq('git_url', gitUrl);
160
- } else {
161
- return {
162
- status: 'no_session',
163
- reason: 'Could not detect git URL and no project_id provided',
164
- continuation_prompt:
165
- 'Could not detect which project you are working on. Please call start_work_session(git_url: "...") with your repository URL.',
166
- };
167
- }
168
-
169
- const { data: project, error: projectError } = await projectQuery.single();
170
-
171
- if (projectError || !project) {
172
- // Project not found - this is OK, might be an untracked repo
173
- return {
174
- status: 'compliant',
175
- reason: `No Vibescope project found for ${gitUrl || projectId}. Untracked repository - exit allowed.`,
176
- details: {
177
- session_started: false,
178
- project_id: null,
179
- project_name: null,
180
- git_url: gitUrl || null,
181
- in_progress_tasks: 0,
182
- tasks_completed_this_session: 0,
183
- progress_logs_this_session: 0,
184
- blockers_logged_this_session: 0,
185
- session_duration_minutes: null,
186
- },
187
- };
188
- }
189
-
190
- // Check for agent session
191
- const { data: session } = await supabase
192
- .from('agent_sessions')
193
- .select('id, last_synced_at, created_at')
194
- .eq('api_key_id', auth.apiKeyId)
195
- .eq('project_id', project.id)
196
- .single();
197
-
198
- if (!session) {
199
- return {
200
- status: 'non_compliant',
201
- reason: 'No Vibescope session started for this project',
202
- continuation_prompt: `[VIBESCOPE ENFORCEMENT] You have not started a Vibescope work session.
203
-
204
- BEFORE you can exit, you MUST call:
205
- start_work_session(git_url: "${project.git_url || project.id}")
206
-
207
- This registers your session and gives you project context.`,
208
- details: {
209
- session_started: false,
210
- project_id: project.id,
211
- project_name: project.name,
212
- git_url: project.git_url,
213
- in_progress_tasks: 0,
214
- tasks_completed_this_session: 0,
215
- progress_logs_this_session: 0,
216
- blockers_logged_this_session: 0,
217
- session_duration_minutes: null,
218
- },
219
- };
220
- }
221
-
222
- const sessionStartTime = new Date(session.created_at);
223
- const lastSyncTime = new Date(session.last_synced_at);
224
- const now = new Date();
225
- const sessionDurationMinutes = Math.round(
226
- (now.getTime() - sessionStartTime.getTime()) / 60000
227
- );
228
-
229
- // Grace period: if session is less than 1 minute, allow exit
230
- if (sessionDurationMinutes < 1) {
231
- return {
232
- status: 'compliant',
233
- reason: 'Very short session (< 1 minute) - exit allowed',
234
- details: {
235
- session_started: true,
236
- project_id: project.id,
237
- project_name: project.name,
238
- git_url: project.git_url,
239
- in_progress_tasks: 0,
240
- tasks_completed_this_session: 0,
241
- progress_logs_this_session: 0,
242
- blockers_logged_this_session: 0,
243
- session_duration_minutes: sessionDurationMinutes,
244
- },
245
- };
246
- }
247
-
248
- // Check for in-progress tasks
249
- const { data: inProgressTasks } = await supabase
250
- .from('tasks')
251
- .select('id, title, progress_percentage')
252
- .eq('project_id', project.id)
253
- .eq('status', 'in_progress');
254
-
255
- const inProgressCount = inProgressTasks?.length || 0;
256
-
257
- if (inProgressCount > 0) {
258
- const taskList = (inProgressTasks as TaskInfo[])
259
- .map((t) => ` - ${t.title} (${t.progress_percentage}% complete)`)
260
- .join('\n');
261
-
262
- return {
263
- status: 'non_compliant',
264
- reason: `You have ${inProgressCount} task(s) still in_progress`,
265
- continuation_prompt: `[VIBESCOPE ENFORCEMENT] You have ${inProgressCount} task(s) still marked as in_progress:
266
-
267
- ${taskList}
268
-
269
- You MUST either:
270
- 1. Complete them: complete_task(task_id: "...", summary: "...")
271
- 2. Log why you're stopping: log_progress(project_id: "${project.id}", summary: "Stopping because...")`,
272
- details: {
273
- session_started: true,
274
- project_id: project.id,
275
- project_name: project.name,
276
- git_url: project.git_url,
277
- in_progress_tasks: inProgressCount,
278
- tasks_completed_this_session: 0,
279
- progress_logs_this_session: 0,
280
- blockers_logged_this_session: 0,
281
- session_duration_minutes: sessionDurationMinutes,
100
+ try {
101
+ const response = await fetch(`${getApiUrl()}/api/mcp/verify`, {
102
+ method: 'POST',
103
+ headers: {
104
+ 'Content-Type': 'application/json',
282
105
  },
283
- };
284
- }
285
-
286
- // Check for activity this session
287
- const sessionStartIso = sessionStartTime.toISOString();
288
-
289
- const [completedResult, progressResult, blockerResult] = await Promise.all([
290
- // Tasks completed after session start
291
- supabase
292
- .from('tasks')
293
- .select('id', { count: 'exact' })
294
- .eq('project_id', project.id)
295
- .eq('status', 'completed')
296
- .gte('completed_at', sessionStartIso),
297
-
298
- // Progress logs by agent after session start
299
- supabase
300
- .from('progress_logs')
301
- .select('id', { count: 'exact' })
302
- .eq('project_id', project.id)
303
- .eq('created_by', 'agent')
304
- .gte('created_at', sessionStartIso),
305
-
306
- // Blockers logged by agent after session start
307
- supabase
308
- .from('blockers')
309
- .select('id', { count: 'exact' })
310
- .eq('project_id', project.id)
311
- .eq('created_by', 'agent')
312
- .gte('created_at', sessionStartIso),
313
- ]);
314
-
315
- const tasksCompleted = completedResult.count || 0;
316
- const progressLogsCount = progressResult.count || 0;
317
- const blockersLogged = blockerResult.count || 0;
318
-
319
- // Check if any tracked work was done
320
- const anyWorkTracked =
321
- tasksCompleted > 0 || progressLogsCount > 0 || blockersLogged > 0;
322
-
323
- if (!anyWorkTracked && sessionDurationMinutes >= 5) {
106
+ body: JSON.stringify({
107
+ api_key: apiKey,
108
+ git_url: gitUrl,
109
+ project_id: projectId,
110
+ }),
111
+ });
112
+
113
+ const result = await response.json() as VerificationResult;
114
+ return result;
115
+ } catch (err) {
324
116
  return {
325
- status: 'non_compliant',
326
- reason: `Session active for ${sessionDurationMinutes} minutes but no work was tracked`,
327
- continuation_prompt: `[VIBESCOPE ENFORCEMENT] Your session has been active for ${sessionDurationMinutes} minutes but no work was tracked.
328
-
329
- The human is watching the dashboard and will see an empty session.
330
-
331
- You MUST either:
332
- 1. Pick a task and work on it: get_next_task(project_id: "${project.id}")
333
- 2. Log why you did nothing: log_progress(project_id: "${project.id}", summary: "No work done because...")`,
334
- details: {
335
- session_started: true,
336
- project_id: project.id,
337
- project_name: project.name,
338
- git_url: project.git_url,
339
- in_progress_tasks: 0,
340
- tasks_completed_this_session: tasksCompleted,
341
- progress_logs_this_session: progressLogsCount,
342
- blockers_logged_this_session: blockersLogged,
343
- session_duration_minutes: sessionDurationMinutes,
344
- },
117
+ status: 'error',
118
+ reason: err instanceof Error ? err.message : 'Network error',
345
119
  };
346
120
  }
347
-
348
- // All checks passed - compliant!
349
- return {
350
- status: 'compliant',
351
- reason: 'All tracked work completed properly',
352
- details: {
353
- session_started: true,
354
- project_id: project.id,
355
- project_name: project.name,
356
- git_url: project.git_url,
357
- in_progress_tasks: 0,
358
- tasks_completed_this_session: tasksCompleted,
359
- progress_logs_this_session: progressLogsCount,
360
- blockers_logged_this_session: blockersLogged,
361
- session_duration_minutes: sessionDurationMinutes,
362
- },
363
- };
364
121
  }
365
122
 
366
123
  // ============================================================================
@@ -413,8 +170,7 @@ Exit Codes:
413
170
 
414
171
  Environment Variables:
415
172
  VIBESCOPE_API_KEY Required - Your Vibescope API key
416
- SUPABASE_URL Required - Supabase project URL
417
- SUPABASE_SERVICE_KEY Required - Supabase service role key
173
+ VIBESCOPE_API_URL Optional - API URL (default: https://vibescope.dev)
418
174
  `);
419
175
  process.exit(0);
420
176
  } else {
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Global Test Setup for Handler Tests
3
+ *
4
+ * This file is loaded before all tests via vitest.config.ts setupFiles.
5
+ * It provides a global mock for the API client used by migrated handlers.
6
+ */
7
+
8
+ import { vi, beforeEach } from 'vitest';
9
+
10
+ // Default success response for API calls
11
+ const defaultSuccess = { ok: true, data: {} };
12
+ const defaultError = { ok: false, error: 'Mock error' };
13
+
14
+ // ============================================================================
15
+ // Global Mock API Client
16
+ // ============================================================================
17
+
18
+ /**
19
+ * Mock API client with all methods stubbed.
20
+ * Tests can configure return values using mockResolvedValue/mockImplementation.
21
+ * Default return is a success response { ok: true, data: {} }
22
+ */
23
+ export const mockApiClient = {
24
+ // Session
25
+ startSession: vi.fn(),
26
+ heartbeat: vi.fn(),
27
+ syncSession: vi.fn(),
28
+ endSession: vi.fn(),
29
+
30
+ // Project
31
+ listProjects: vi.fn(),
32
+ getProject: vi.fn(),
33
+ getProjectContext: vi.fn(),
34
+ createProject: vi.fn(),
35
+ updateProject: vi.fn(),
36
+ updateProjectReadme: vi.fn(),
37
+ getGitWorkflow: vi.fn(),
38
+
39
+ // Tasks
40
+ getTasks: vi.fn(),
41
+ getNextTask: vi.fn(),
42
+ createTask: vi.fn(),
43
+ updateTask: vi.fn(),
44
+ completeTask: vi.fn(),
45
+ deleteTask: vi.fn(),
46
+ addTaskReference: vi.fn(),
47
+ removeTaskReference: vi.fn(),
48
+ batchUpdateTasks: vi.fn(),
49
+ batchCompleteTasks: vi.fn(),
50
+
51
+ // Subtasks
52
+ addSubtask: vi.fn(),
53
+ getSubtasks: vi.fn(),
54
+
55
+ // Progress
56
+ logProgress: vi.fn(),
57
+ getActivityFeed: vi.fn(),
58
+
59
+ // Blockers
60
+ addBlocker: vi.fn(),
61
+ resolveBlocker: vi.fn(),
62
+ getBlockers: vi.fn(),
63
+ deleteBlocker: vi.fn(),
64
+
65
+ // Decisions
66
+ logDecision: vi.fn(),
67
+ getDecisions: vi.fn(),
68
+ deleteDecision: vi.fn(),
69
+
70
+ // Ideas
71
+ addIdea: vi.fn(),
72
+ updateIdea: vi.fn(),
73
+ getIdeas: vi.fn(),
74
+ deleteIdea: vi.fn(),
75
+ convertIdeaToTask: vi.fn(),
76
+
77
+ // Findings
78
+ addFinding: vi.fn(),
79
+ getFindings: vi.fn(),
80
+ updateFinding: vi.fn(),
81
+ deleteFinding: vi.fn(),
82
+
83
+ // Requests
84
+ getPendingRequests: vi.fn(),
85
+ acknowledgeRequest: vi.fn(),
86
+ answerQuestion: vi.fn(),
87
+
88
+ // Milestones
89
+ addMilestone: vi.fn(),
90
+ updateMilestone: vi.fn(),
91
+ completeMilestone: vi.fn(),
92
+ deleteMilestone: vi.fn(),
93
+ getMilestones: vi.fn(),
94
+
95
+ // Fallback
96
+ startFallbackActivity: vi.fn(),
97
+ stopFallbackActivity: vi.fn(),
98
+ getActivityHistory: vi.fn(),
99
+ getActivitySchedules: vi.fn(),
100
+
101
+ // Cost
102
+ getCostSummary: vi.fn(),
103
+ getCostAlerts: vi.fn(),
104
+ addCostAlert: vi.fn(),
105
+ updateCostAlert: vi.fn(),
106
+ deleteCostAlert: vi.fn(),
107
+ getTaskCosts: vi.fn(),
108
+
109
+ // Git Issues
110
+ addGitIssue: vi.fn(),
111
+ resolveGitIssue: vi.fn(),
112
+ getGitIssues: vi.fn(),
113
+ deleteGitIssue: vi.fn(),
114
+
115
+ // Organizations
116
+ listOrganizations: vi.fn(),
117
+ createOrganization: vi.fn(),
118
+ updateOrganization: vi.fn(),
119
+ deleteOrganization: vi.fn(),
120
+ listOrgMembers: vi.fn(),
121
+ inviteMember: vi.fn(),
122
+ updateMemberRole: vi.fn(),
123
+ removeMember: vi.fn(),
124
+ leaveOrganization: vi.fn(),
125
+ shareProjectWithOrg: vi.fn(),
126
+ updateProjectShare: vi.fn(),
127
+ unshareProject: vi.fn(),
128
+ listProjectShares: vi.fn(),
129
+
130
+ // Bodies of Work
131
+ createBodyOfWork: vi.fn(),
132
+ updateBodyOfWork: vi.fn(),
133
+ getBodyOfWork: vi.fn(),
134
+ getBodiesOfWork: vi.fn(),
135
+ deleteBodyOfWork: vi.fn(),
136
+ addTaskToBodyOfWork: vi.fn(),
137
+ removeTaskFromBodyOfWork: vi.fn(),
138
+ activateBodyOfWork: vi.fn(),
139
+ addTaskDependency: vi.fn(),
140
+ removeTaskDependency: vi.fn(),
141
+ getTaskDependencies: vi.fn(),
142
+ getNextBodyOfWorkTask: vi.fn(),
143
+
144
+ // Deployment
145
+ requestDeployment: vi.fn(),
146
+ claimDeploymentValidation: vi.fn(),
147
+ reportValidation: vi.fn(),
148
+ checkDeploymentStatus: vi.fn(),
149
+ startDeployment: vi.fn(),
150
+ completeDeployment: vi.fn(),
151
+ cancelDeployment: vi.fn(),
152
+ addDeploymentRequirement: vi.fn(),
153
+ completeDeploymentRequirement: vi.fn(),
154
+ getDeploymentRequirements: vi.fn(),
155
+ scheduleDeployment: vi.fn(),
156
+ getScheduledDeployments: vi.fn(),
157
+ updateScheduledDeployment: vi.fn(),
158
+ deleteScheduledDeployment: vi.fn(),
159
+ triggerScheduledDeployment: vi.fn(),
160
+ checkDueDeployments: vi.fn(),
161
+
162
+ // Validation
163
+ getTasksAwaitingValidation: vi.fn(),
164
+ claimValidation: vi.fn(),
165
+ validateTask: vi.fn(),
166
+
167
+ // Proxy (generic)
168
+ proxy: vi.fn(),
169
+ };
170
+
171
+ // ============================================================================
172
+ // Mock the API Client Module
173
+ // ============================================================================
174
+
175
+ // Note: The path is relative to the test file location, not this setup file
176
+ // This setup file is in src/handlers/, tests import from ../api-client.js
177
+ vi.mock('../api-client.js', () => ({
178
+ getApiClient: () => mockApiClient,
179
+ initApiClient: vi.fn(),
180
+ VibescopeApiClient: vi.fn(),
181
+ }));
182
+
183
+ // Also mock with absolute-style path for tests outside handlers/
184
+ vi.mock('../../api-client.js', () => ({
185
+ getApiClient: () => mockApiClient,
186
+ initApiClient: vi.fn(),
187
+ VibescopeApiClient: vi.fn(),
188
+ }));
189
+
190
+ // Export for test files that need direct access
191
+ export { mockApiClient as __mockApiClient__ };
192
+
193
+ // ============================================================================
194
+ // Reset Mocks Before Each Test
195
+ // ============================================================================
196
+
197
+ /**
198
+ * Reset all mock functions before each test and set default return values.
199
+ * This ensures tests start with a clean slate and have sensible defaults.
200
+ */
201
+ beforeEach(() => {
202
+ // Reset all mocks
203
+ Object.values(mockApiClient).forEach((mockFn) => {
204
+ if (typeof mockFn === 'function' && mockFn.mockReset) {
205
+ mockFn.mockReset();
206
+ // Set default return value to success
207
+ mockFn.mockResolvedValue({ ok: true, data: {} });
208
+ }
209
+ });
210
+ });