@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/index.js CHANGED
@@ -2,11 +2,10 @@
2
2
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
- import { createClient } from '@supabase/supabase-js';
6
5
  import { randomUUID } from 'crypto';
6
+ import { initApiClient, getApiClient } from './api-client.js';
7
7
  import { ValidationError, } from './validators.js';
8
8
  import { RateLimiter, } from './utils.js';
9
- import { hashApiKey } from './cli.js';
10
9
  import { buildHandlerRegistry } from './handlers/index.js';
11
10
  // ============================================================================
12
11
  // Agent Instance Tracking
@@ -17,6 +16,8 @@ const INSTANCE_ID = randomUUID();
17
16
  let currentSessionId = null;
18
17
  // Assigned persona for this agent instance
19
18
  let currentPersona = null;
19
+ // Current role for this agent instance
20
+ let currentRole = null;
20
21
  let sessionTokenUsage = {
21
22
  callCount: 0,
22
23
  totalTokens: 0,
@@ -152,158 +153,29 @@ Be mindful of token costs - every tool call has a cost.
152
153
  // ============================================================================
153
154
  // Configuration
154
155
  // ============================================================================
155
- // Default Supabase URL for Vibescope production instance
156
- const DEFAULT_SUPABASE_URL = 'https://uuneucmuubpgswvfijwd.supabase.co';
157
- const SUPABASE_URL = process.env.SUPABASE_URL || process.env.PUBLIC_SUPABASE_URL || DEFAULT_SUPABASE_URL;
158
- const SUPABASE_SERVICE_KEY = process.env.SUPABASE_SERVICE_KEY;
159
156
  const API_KEY = process.env.VIBESCOPE_API_KEY;
160
- if (!SUPABASE_SERVICE_KEY) {
161
- console.error('Missing required environment variable: SUPABASE_SERVICE_KEY');
162
- process.exit(1);
163
- }
164
157
  if (!API_KEY) {
165
158
  console.error('Missing required environment variable: VIBESCOPE_API_KEY');
166
159
  process.exit(1);
167
160
  }
168
- // ============================================================================
169
- // Database Client
170
- // ============================================================================
171
- // Using untyped client for MCP server - data validation happens at runtime
172
- const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_KEY);
161
+ // Initialize the API client
162
+ initApiClient({ apiKey: API_KEY });
173
163
  // ============================================================================
174
164
  // Authentication
175
165
  // ============================================================================
176
- async function validateApiKey(apiKey) {
177
- const keyHash = hashApiKey(apiKey);
178
- const { data, error } = await supabase
179
- .from('api_keys')
180
- .select('id, user_id, organization_id, scope, is_valid')
181
- .eq('key_hash', keyHash)
182
- .single();
183
- if (error || !data) {
184
- return null;
185
- }
186
- // Check if key has been invalidated (e.g., user left org)
187
- if (data.is_valid === false) {
166
+ async function validateApiKey() {
167
+ const apiClient = getApiClient();
168
+ const response = await apiClient.validateAuth();
169
+ if (!response.ok || !response.data?.valid) {
188
170
  return null;
189
171
  }
190
- // For org-scoped keys, verify user is still a member
191
- if (data.scope === 'organization' && data.organization_id) {
192
- const { data: member } = await supabase
193
- .from('organization_members')
194
- .select('role')
195
- .eq('organization_id', data.organization_id)
196
- .eq('user_id', data.user_id)
197
- .single();
198
- if (!member) {
199
- // User is no longer a member, invalidate the key
200
- await supabase
201
- .from('api_keys')
202
- .update({ is_valid: false })
203
- .eq('id', data.id);
204
- return null;
205
- }
206
- }
207
- // Update last_used_at
208
- await supabase
209
- .from('api_keys')
210
- .update({ last_used_at: new Date().toISOString() })
211
- .eq('id', data.id);
212
172
  return {
213
- userId: data.user_id,
214
- apiKeyId: data.id,
215
- organizationId: data.organization_id || undefined,
216
- scope: data.scope || 'personal',
173
+ userId: response.data.user_id,
174
+ apiKeyId: response.data.api_key_id,
175
+ scope: 'personal', // API handles authorization, scope not needed locally
217
176
  };
218
177
  }
219
178
  // ============================================================================
220
- // Session Status Checking (Multi-Agent Coordination)
221
- // ============================================================================
222
- /**
223
- * Check if an agent session is active or stale.
224
- * A session is considered stale if:
225
- * - It doesn't exist (orphaned reference)
226
- * - Its status is 'disconnected'
227
- * - Its last_synced_at is older than 5 minutes
228
- */
229
- async function checkSessionStatus(sessionId) {
230
- const { data: session } = await supabase
231
- .from('agent_sessions')
232
- .select('id, status, last_synced_at, agent_name, instance_id')
233
- .eq('id', sessionId)
234
- .single();
235
- if (!session) {
236
- return { exists: false, isActive: false };
237
- }
238
- const lastSync = new Date(session.last_synced_at).getTime();
239
- const fiveMinutesAgo = Date.now() - 5 * 60 * 1000;
240
- const isActive = session.status !== 'disconnected' && lastSync > fiveMinutesAgo;
241
- return {
242
- exists: true,
243
- isActive,
244
- agentName: session.agent_name || `Agent ${session.instance_id?.slice(0, 8) || sessionId.slice(0, 8)}`,
245
- };
246
- }
247
- // ============================================================================
248
- // User Updates Tracking
249
- // ============================================================================
250
- async function getUserUpdates(auth, projectId) {
251
- // Get session's last_synced_at - prefer currentSessionId for accuracy
252
- let lastSyncedAt;
253
- if (currentSessionId) {
254
- const { data: session } = await supabase
255
- .from('agent_sessions')
256
- .select('last_synced_at')
257
- .eq('id', currentSessionId)
258
- .single();
259
- lastSyncedAt = session?.last_synced_at || new Date(0).toISOString();
260
- }
261
- else {
262
- const { data: session } = await supabase
263
- .from('agent_sessions')
264
- .select('last_synced_at')
265
- .eq('api_key_id', auth.apiKeyId)
266
- .eq('project_id', projectId)
267
- .single();
268
- lastSyncedAt = session?.last_synced_at || new Date(0).toISOString();
269
- }
270
- // Fetch user-created items since last sync (limit to 5 each for context efficiency)
271
- const [tasksResult, blockersResult, ideasResult] = await Promise.all([
272
- supabase
273
- .from('tasks')
274
- .select('id, title, created_at')
275
- .eq('project_id', projectId)
276
- .eq('created_by', 'user')
277
- .gt('created_at', lastSyncedAt)
278
- .order('created_at', { ascending: false })
279
- .limit(5),
280
- supabase
281
- .from('blockers')
282
- .select('id, description, created_at')
283
- .eq('project_id', projectId)
284
- .eq('created_by', 'user')
285
- .gt('created_at', lastSyncedAt)
286
- .order('created_at', { ascending: false })
287
- .limit(5),
288
- supabase
289
- .from('ideas')
290
- .select('id, title, created_at')
291
- .eq('project_id', projectId)
292
- .eq('created_by', 'user')
293
- .gt('created_at', lastSyncedAt)
294
- .order('created_at', { ascending: false })
295
- .limit(5),
296
- ]);
297
- const tasks = tasksResult.data || [];
298
- const blockers = blockersResult.data || [];
299
- const ideas = ideasResult.data || [];
300
- // Return undefined if no new updates (saves context window space)
301
- if (tasks.length === 0 && blockers.length === 0 && ideas.length === 0) {
302
- return undefined;
303
- }
304
- return { tasks, blockers, ideas };
305
- }
306
- // ============================================================================
307
179
  // Tool Definitions
308
180
  // ============================================================================
309
181
  const tools = [
@@ -343,7 +215,7 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
343
215
  properties: {
344
216
  topic: {
345
217
  type: 'string',
346
- enum: ['getting_started', 'tasks', 'validation', 'deployment', 'git', 'blockers', 'milestones', 'fallback', 'session', 'tokens', 'topics'],
218
+ enum: ['getting_started', 'tasks', 'validation', 'deployment', 'git', 'blockers', 'milestones', 'fallback', 'session', 'tokens', 'sprints', 'topics'],
347
219
  description: 'Help topic. Use "topics" to list all available.',
348
220
  },
349
221
  },
@@ -669,6 +541,14 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
669
541
  type: 'number',
670
542
  description: 'Max number of tasks to return (default 50)',
671
543
  },
544
+ offset: {
545
+ type: 'number',
546
+ description: 'Number of tasks to skip for pagination (default 0)',
547
+ },
548
+ search_query: {
549
+ type: 'string',
550
+ description: 'Search tasks by title',
551
+ },
672
552
  include_subtasks: {
673
553
  type: 'boolean',
674
554
  description: 'Include subtasks in results (default false). Use get_subtasks for subtasks of a specific task.',
@@ -724,6 +604,11 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
724
604
  type: 'boolean',
725
605
  description: 'When true, this task blocks all other work until complete (used for deployment finalization)',
726
606
  },
607
+ task_type: {
608
+ type: 'string',
609
+ enum: ['frontend', 'backend', 'database', 'feature', 'bugfix', 'design', 'mcp', 'testing', 'docs', 'infra', 'other'],
610
+ description: 'Task category (frontend, backend, database, feature, bugfix, design, mcp, testing, docs, infra, other)',
611
+ },
727
612
  },
728
613
  required: ['project_id', 'title'],
729
614
  },
@@ -768,6 +653,11 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
768
653
  type: 'string',
769
654
  description: 'Git branch associated with this task',
770
655
  },
656
+ task_type: {
657
+ type: 'string',
658
+ enum: ['frontend', 'backend', 'database', 'feature', 'bugfix', 'design', 'mcp', 'testing', 'docs', 'infra', 'other'],
659
+ description: 'Task category (frontend, backend, database, feature, bugfix, design, mcp, testing, docs, infra, other)',
660
+ },
771
661
  },
772
662
  required: ['task_id'],
773
663
  },
@@ -867,6 +757,18 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
867
757
  enum: ['open', 'resolved'],
868
758
  description: 'Filter by status (default: open)',
869
759
  },
760
+ limit: {
761
+ type: 'number',
762
+ description: 'Max number of blockers to return (default 50, max 200)',
763
+ },
764
+ offset: {
765
+ type: 'number',
766
+ description: 'Number of blockers to skip for pagination (default 0)',
767
+ },
768
+ search_query: {
769
+ type: 'string',
770
+ description: 'Search blockers by description',
771
+ },
870
772
  },
871
773
  required: ['project_id'],
872
774
  },
@@ -912,6 +814,18 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
912
814
  type: 'string',
913
815
  description: 'Project UUID',
914
816
  },
817
+ limit: {
818
+ type: 'number',
819
+ description: 'Max number of decisions to return (default 50, max 200)',
820
+ },
821
+ offset: {
822
+ type: 'number',
823
+ description: 'Number of decisions to skip for pagination (default 0)',
824
+ },
825
+ search_query: {
826
+ type: 'string',
827
+ description: 'Search decisions by title or description',
828
+ },
915
829
  },
916
830
  required: ['project_id'],
917
831
  },
@@ -989,6 +903,18 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
989
903
  enum: ['raw', 'exploring', 'planned', 'in_development', 'shipped'],
990
904
  description: 'Filter by status (optional)',
991
905
  },
906
+ limit: {
907
+ type: 'number',
908
+ description: 'Max number of ideas to return (default 50, max 100)',
909
+ },
910
+ offset: {
911
+ type: 'number',
912
+ description: 'Number of ideas to skip for pagination (default 0)',
913
+ },
914
+ search_query: {
915
+ type: 'string',
916
+ description: 'Search ideas by title or description',
917
+ },
992
918
  },
993
919
  required: ['project_id'],
994
920
  },
@@ -1119,7 +1045,9 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
1119
1045
  category: { type: 'string', enum: ['performance', 'security', 'code_quality', 'accessibility', 'documentation', 'architecture', 'testing', 'other'], description: 'Filter by category (optional)' },
1120
1046
  severity: { type: 'string', enum: ['info', 'low', 'medium', 'high', 'critical'], description: 'Filter by severity (optional)' },
1121
1047
  status: { type: 'string', enum: ['open', 'addressed', 'dismissed', 'wontfix'], description: 'Filter by status (default: all)' },
1122
- limit: { type: 'number', description: 'Max number of findings to return (default 50)' },
1048
+ limit: { type: 'number', description: 'Max number of findings to return (default 50, max 200)' },
1049
+ offset: { type: 'number', description: 'Number of findings to skip for pagination (default 0)' },
1050
+ search_query: { type: 'string', description: 'Search findings by title or description' },
1123
1051
  },
1124
1052
  required: ['project_id'],
1125
1053
  },
@@ -1165,6 +1093,119 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
1165
1093
  required: ['blocker_id'],
1166
1094
  },
1167
1095
  },
1096
+ // Git Issues tools
1097
+ {
1098
+ name: 'add_git_issue',
1099
+ description: `Record a git-related issue (merge conflict, push failure, etc.). Auto-created by claim_validation when conflicts detected.`,
1100
+ inputSchema: {
1101
+ type: 'object',
1102
+ properties: {
1103
+ project_id: {
1104
+ type: 'string',
1105
+ description: 'Project UUID',
1106
+ },
1107
+ issue_type: {
1108
+ type: 'string',
1109
+ enum: ['merge_conflict', 'push_failed', 'rebase_needed', 'branch_diverged', 'pr_not_mergeable'],
1110
+ description: 'Type of git issue',
1111
+ },
1112
+ branch: {
1113
+ type: 'string',
1114
+ description: 'Branch where the issue occurred',
1115
+ },
1116
+ target_branch: {
1117
+ type: 'string',
1118
+ description: 'Target branch for merge/rebase (optional)',
1119
+ },
1120
+ pr_url: {
1121
+ type: 'string',
1122
+ description: 'Pull request URL if applicable (optional)',
1123
+ },
1124
+ conflicting_files: {
1125
+ type: 'array',
1126
+ items: { type: 'string' },
1127
+ description: 'List of files with conflicts (optional)',
1128
+ },
1129
+ error_message: {
1130
+ type: 'string',
1131
+ description: 'Error message from git operation (optional)',
1132
+ },
1133
+ task_id: {
1134
+ type: 'string',
1135
+ description: 'Related task UUID (optional)',
1136
+ },
1137
+ },
1138
+ required: ['project_id', 'issue_type', 'branch'],
1139
+ },
1140
+ },
1141
+ {
1142
+ name: 'resolve_git_issue',
1143
+ description: `Mark a git issue as resolved.`,
1144
+ inputSchema: {
1145
+ type: 'object',
1146
+ properties: {
1147
+ git_issue_id: {
1148
+ type: 'string',
1149
+ description: 'Git issue UUID',
1150
+ },
1151
+ resolution_note: {
1152
+ type: 'string',
1153
+ description: 'How the issue was resolved (optional)',
1154
+ },
1155
+ auto_resolved: {
1156
+ type: 'boolean',
1157
+ description: 'Whether this was auto-resolved (e.g., PR became mergeable)',
1158
+ },
1159
+ },
1160
+ required: ['git_issue_id'],
1161
+ },
1162
+ },
1163
+ {
1164
+ name: 'get_git_issues',
1165
+ description: `Get git issues for a project, optionally filtered by status, type, or branch.`,
1166
+ inputSchema: {
1167
+ type: 'object',
1168
+ properties: {
1169
+ project_id: {
1170
+ type: 'string',
1171
+ description: 'Project UUID',
1172
+ },
1173
+ status: {
1174
+ type: 'string',
1175
+ enum: ['open', 'resolved'],
1176
+ description: 'Filter by status (default: open)',
1177
+ },
1178
+ issue_type: {
1179
+ type: 'string',
1180
+ enum: ['merge_conflict', 'push_failed', 'rebase_needed', 'branch_diverged', 'pr_not_mergeable'],
1181
+ description: 'Filter by issue type (optional)',
1182
+ },
1183
+ branch: {
1184
+ type: 'string',
1185
+ description: 'Filter by branch (optional)',
1186
+ },
1187
+ limit: {
1188
+ type: 'number',
1189
+ description: 'Max issues to return (default: 50)',
1190
+ },
1191
+ },
1192
+ required: ['project_id'],
1193
+ },
1194
+ },
1195
+ {
1196
+ name: 'delete_git_issue',
1197
+ description: `Delete a git issue.`,
1198
+ inputSchema: {
1199
+ type: 'object',
1200
+ properties: {
1201
+ git_issue_id: {
1202
+ type: 'string',
1203
+ description: 'Git issue UUID',
1204
+ },
1205
+ },
1206
+ required: ['git_issue_id'],
1207
+ },
1208
+ },
1168
1209
  {
1169
1210
  name: 'delete_decision',
1170
1211
  description: `Delete a decision.`,
@@ -1330,6 +1371,10 @@ Returns subtasks with aggregate completion stats.`,
1330
1371
  type: 'string',
1331
1372
  description: 'Session ID from start_work_session (optional, uses current session if not provided)',
1332
1373
  },
1374
+ current_worktree_path: {
1375
+ type: ['string', 'null'],
1376
+ description: 'Report your current git worktree path (e.g., "../project-task-abc123"). Set to null to clear.',
1377
+ },
1333
1378
  },
1334
1379
  },
1335
1380
  },
@@ -1379,7 +1424,7 @@ Returns subtasks with aggregate completion stats.`,
1379
1424
  },
1380
1425
  {
1381
1426
  name: 'validate_task',
1382
- description: 'Validate a completed task. Include test results in validation_notes.',
1427
+ description: 'Validate a completed task. Include test results in validation_notes. For github-flow/git-flow projects, a PR must exist before approval (add via add_task_reference).',
1383
1428
  inputSchema: {
1384
1429
  type: 'object',
1385
1430
  properties: {
@@ -1395,6 +1440,10 @@ Returns subtasks with aggregate completion stats.`,
1395
1440
  type: 'boolean',
1396
1441
  description: 'Whether the task passes validation (true = approved, false = needs more work)',
1397
1442
  },
1443
+ skip_pr_check: {
1444
+ type: 'boolean',
1445
+ description: 'Skip PR existence check (use only for tasks that legitimately do not need a PR)',
1446
+ },
1398
1447
  },
1399
1448
  required: ['task_id', 'approved'],
1400
1449
  },
@@ -2102,7 +2151,8 @@ Bodies of work allow organizing related tasks with optional auto-deployment on c
2102
2151
  },
2103
2152
  {
2104
2153
  name: 'get_body_of_work',
2105
- description: `Get a body of work with all its tasks organized by phase.`,
2154
+ description: `Get a body of work with all its tasks organized by phase.
2155
+ Use summary_only: true to get task counts and next task instead of full task arrays (saves tokens).`,
2106
2156
  inputSchema: {
2107
2157
  type: 'object',
2108
2158
  properties: {
@@ -2110,6 +2160,10 @@ Bodies of work allow organizing related tasks with optional auto-deployment on c
2110
2160
  type: 'string',
2111
2161
  description: 'Body of work UUID',
2112
2162
  },
2163
+ summary_only: {
2164
+ type: 'boolean',
2165
+ description: 'Return task counts and next task instead of full task arrays (default: false)',
2166
+ },
2113
2167
  },
2114
2168
  required: ['body_of_work_id'],
2115
2169
  },
@@ -2129,6 +2183,18 @@ Bodies of work allow organizing related tasks with optional auto-deployment on c
2129
2183
  enum: ['draft', 'active', 'completed', 'cancelled'],
2130
2184
  description: 'Filter by status (optional)',
2131
2185
  },
2186
+ limit: {
2187
+ type: 'number',
2188
+ description: 'Max number of bodies of work to return (default 50, max 100)',
2189
+ },
2190
+ offset: {
2191
+ type: 'number',
2192
+ description: 'Number of items to skip for pagination (default 0)',
2193
+ },
2194
+ search_query: {
2195
+ type: 'string',
2196
+ description: 'Search bodies of work by title or description',
2197
+ },
2132
2198
  },
2133
2199
  required: ['project_id'],
2134
2200
  },
@@ -2279,6 +2345,278 @@ Only returns tasks where all dependencies are completed.`,
2279
2345
  },
2280
2346
  },
2281
2347
  // ============================================================================
2348
+ // Sprint Tools
2349
+ // ============================================================================
2350
+ {
2351
+ name: 'create_sprint',
2352
+ description: `Create a new sprint. Sprints are time-bounded bodies of work with velocity tracking.
2353
+ Sprints start in 'planning' status where tasks can be added with story points.`,
2354
+ inputSchema: {
2355
+ type: 'object',
2356
+ properties: {
2357
+ project_id: {
2358
+ type: 'string',
2359
+ description: 'Project UUID',
2360
+ },
2361
+ title: {
2362
+ type: 'string',
2363
+ description: 'Sprint title (e.g., "Sprint 5" or "Q1 Release")',
2364
+ },
2365
+ goal: {
2366
+ type: 'string',
2367
+ description: 'Sprint goal statement',
2368
+ },
2369
+ start_date: {
2370
+ type: 'string',
2371
+ description: 'Start date (YYYY-MM-DD)',
2372
+ },
2373
+ end_date: {
2374
+ type: 'string',
2375
+ description: 'End date (YYYY-MM-DD)',
2376
+ },
2377
+ auto_deploy_on_completion: {
2378
+ type: 'boolean',
2379
+ description: 'Automatically request deployment when sprint completes (default: false)',
2380
+ },
2381
+ deploy_environment: {
2382
+ type: 'string',
2383
+ enum: ['development', 'staging', 'production'],
2384
+ description: 'Target environment for auto-deploy (default: production)',
2385
+ },
2386
+ deploy_version_bump: {
2387
+ type: 'string',
2388
+ enum: ['patch', 'minor', 'major'],
2389
+ description: 'Version bump for auto-deploy (default: minor)',
2390
+ },
2391
+ },
2392
+ required: ['project_id', 'title', 'start_date', 'end_date'],
2393
+ },
2394
+ },
2395
+ {
2396
+ name: 'update_sprint',
2397
+ description: `Update a sprint's details. Can update title, goal, dates, and deployment settings.`,
2398
+ inputSchema: {
2399
+ type: 'object',
2400
+ properties: {
2401
+ sprint_id: {
2402
+ type: 'string',
2403
+ description: 'Sprint UUID',
2404
+ },
2405
+ title: {
2406
+ type: 'string',
2407
+ description: 'New sprint title',
2408
+ },
2409
+ goal: {
2410
+ type: 'string',
2411
+ description: 'New sprint goal',
2412
+ },
2413
+ start_date: {
2414
+ type: 'string',
2415
+ description: 'New start date (YYYY-MM-DD)',
2416
+ },
2417
+ end_date: {
2418
+ type: 'string',
2419
+ description: 'New end date (YYYY-MM-DD)',
2420
+ },
2421
+ auto_deploy_on_completion: {
2422
+ type: 'boolean',
2423
+ description: 'Auto-deploy setting',
2424
+ },
2425
+ deploy_environment: {
2426
+ type: 'string',
2427
+ enum: ['development', 'staging', 'production'],
2428
+ description: 'Target environment',
2429
+ },
2430
+ deploy_version_bump: {
2431
+ type: 'string',
2432
+ enum: ['patch', 'minor', 'major'],
2433
+ description: 'Version bump type',
2434
+ },
2435
+ },
2436
+ required: ['sprint_id'],
2437
+ },
2438
+ },
2439
+ {
2440
+ name: 'get_sprint',
2441
+ description: `Get a sprint with all its tasks organized by phase (pre/core/post).
2442
+ Includes progress percentage, velocity points, and committed points.
2443
+ Use summary_only: true to get task counts and next task instead of full task arrays (saves tokens).`,
2444
+ inputSchema: {
2445
+ type: 'object',
2446
+ properties: {
2447
+ sprint_id: {
2448
+ type: 'string',
2449
+ description: 'Sprint UUID',
2450
+ },
2451
+ summary_only: {
2452
+ type: 'boolean',
2453
+ description: 'Return task counts and next task instead of full task arrays (default: false)',
2454
+ },
2455
+ },
2456
+ required: ['sprint_id'],
2457
+ },
2458
+ },
2459
+ {
2460
+ name: 'get_sprints',
2461
+ description: `List sprints for a project with velocity metrics.
2462
+ Returns sprints sorted by sprint_number descending (most recent first).`,
2463
+ inputSchema: {
2464
+ type: 'object',
2465
+ properties: {
2466
+ project_id: {
2467
+ type: 'string',
2468
+ description: 'Project UUID',
2469
+ },
2470
+ status: {
2471
+ type: 'string',
2472
+ enum: ['planning', 'active', 'in_review', 'retrospective', 'completed', 'cancelled'],
2473
+ description: 'Filter by sprint status (optional)',
2474
+ },
2475
+ limit: {
2476
+ type: 'number',
2477
+ description: 'Max sprints to return (default: 20, max: 100)',
2478
+ },
2479
+ },
2480
+ required: ['project_id'],
2481
+ },
2482
+ },
2483
+ {
2484
+ name: 'delete_sprint',
2485
+ description: `Delete a sprint. Tasks are preserved but no longer grouped.`,
2486
+ inputSchema: {
2487
+ type: 'object',
2488
+ properties: {
2489
+ sprint_id: {
2490
+ type: 'string',
2491
+ description: 'Sprint UUID',
2492
+ },
2493
+ },
2494
+ required: ['sprint_id'],
2495
+ },
2496
+ },
2497
+ {
2498
+ name: 'start_sprint',
2499
+ description: `Start a sprint. Transitions from 'planning' to 'active' status.
2500
+ Locks the committed_points at the current total story points.`,
2501
+ inputSchema: {
2502
+ type: 'object',
2503
+ properties: {
2504
+ sprint_id: {
2505
+ type: 'string',
2506
+ description: 'Sprint UUID',
2507
+ },
2508
+ },
2509
+ required: ['sprint_id'],
2510
+ },
2511
+ },
2512
+ {
2513
+ name: 'complete_sprint',
2514
+ description: `Complete a sprint. Handles retrospective phase and auto-deployment if configured.
2515
+ Status flow: active → in_review → retrospective → completed`,
2516
+ inputSchema: {
2517
+ type: 'object',
2518
+ properties: {
2519
+ sprint_id: {
2520
+ type: 'string',
2521
+ description: 'Sprint UUID',
2522
+ },
2523
+ retrospective_notes: {
2524
+ type: 'string',
2525
+ description: 'Sprint retrospective notes',
2526
+ },
2527
+ skip_retrospective: {
2528
+ type: 'boolean',
2529
+ description: 'Skip retrospective phase and go directly to completed (default: false)',
2530
+ },
2531
+ },
2532
+ required: ['sprint_id'],
2533
+ },
2534
+ },
2535
+ {
2536
+ name: 'add_task_to_sprint',
2537
+ description: `Add a task to a sprint with optional story points.
2538
+ Tasks can be added during 'planning' status. Story points contribute to committed_points.`,
2539
+ inputSchema: {
2540
+ type: 'object',
2541
+ properties: {
2542
+ sprint_id: {
2543
+ type: 'string',
2544
+ description: 'Sprint UUID',
2545
+ },
2546
+ task_id: {
2547
+ type: 'string',
2548
+ description: 'Task UUID to add',
2549
+ },
2550
+ story_points: {
2551
+ type: 'number',
2552
+ description: 'Story point estimate (optional, must be non-negative integer)',
2553
+ },
2554
+ phase: {
2555
+ type: 'string',
2556
+ enum: ['pre', 'core', 'post'],
2557
+ description: 'Task phase (default: core)',
2558
+ },
2559
+ },
2560
+ required: ['sprint_id', 'task_id'],
2561
+ },
2562
+ },
2563
+ {
2564
+ name: 'remove_task_from_sprint',
2565
+ description: `Remove a task from a sprint. Task is preserved but returns to backlog.`,
2566
+ inputSchema: {
2567
+ type: 'object',
2568
+ properties: {
2569
+ sprint_id: {
2570
+ type: 'string',
2571
+ description: 'Sprint UUID',
2572
+ },
2573
+ task_id: {
2574
+ type: 'string',
2575
+ description: 'Task UUID to remove',
2576
+ },
2577
+ },
2578
+ required: ['sprint_id', 'task_id'],
2579
+ },
2580
+ },
2581
+ {
2582
+ name: 'get_sprint_backlog',
2583
+ description: `Get tasks from backlog/pending that can be added to a sprint.
2584
+ Returns tasks not already assigned to any body of work or sprint.`,
2585
+ inputSchema: {
2586
+ type: 'object',
2587
+ properties: {
2588
+ project_id: {
2589
+ type: 'string',
2590
+ description: 'Project UUID',
2591
+ },
2592
+ sprint_id: {
2593
+ type: 'string',
2594
+ description: 'Sprint UUID to exclude already-added tasks (optional)',
2595
+ },
2596
+ },
2597
+ required: ['project_id'],
2598
+ },
2599
+ },
2600
+ {
2601
+ name: 'get_sprint_velocity',
2602
+ description: `Get velocity metrics for completed sprints.
2603
+ Returns committed vs completed points and average velocity.`,
2604
+ inputSchema: {
2605
+ type: 'object',
2606
+ properties: {
2607
+ project_id: {
2608
+ type: 'string',
2609
+ description: 'Project UUID',
2610
+ },
2611
+ limit: {
2612
+ type: 'number',
2613
+ description: 'Number of sprints to analyze (default: 10, max: 50)',
2614
+ },
2615
+ },
2616
+ required: ['project_id'],
2617
+ },
2618
+ },
2619
+ // ============================================================================
2282
2620
  // Organization Tools
2283
2621
  // ============================================================================
2284
2622
  {
@@ -2526,24 +2864,15 @@ Only returns tasks where all dependencies are completed.`,
2526
2864
  // Tool Handlers
2527
2865
  // ============================================================================
2528
2866
  async function handleTool(auth, name, args) {
2529
- // Update session on every tool call:
2867
+ // Update session on every tool call via API:
2530
2868
  // - last_synced_at: keeps session alive and visible as "active" on dashboard
2531
- // - tool_call_count: incremented for token efficiency tracking
2532
- // - last_tool_at: timestamp of last tool call
2869
+ // - token tracking: synced periodically for cost monitoring
2533
2870
  // Skip for start_work_session since it handles its own session setup
2534
2871
  if (currentSessionId && name !== 'start_work_session') {
2535
- const now = new Date().toISOString();
2536
- await supabase.rpc('increment_tool_call_count', {
2537
- p_session_id: currentSessionId,
2538
- p_timestamp: now
2539
- }).then(({ error }) => {
2540
- // Fallback to direct update if RPC doesn't exist (pre-migration)
2541
- if (error) {
2542
- return supabase
2543
- .from('agent_sessions')
2544
- .update({ last_synced_at: now })
2545
- .eq('id', currentSessionId);
2546
- }
2872
+ const apiClient = getApiClient();
2873
+ // Fire and forget - don't block tool execution on session sync
2874
+ apiClient.syncSession(currentSessionId).catch(() => {
2875
+ // Silently ignore sync errors - session tracking is non-critical
2547
2876
  });
2548
2877
  }
2549
2878
  // Check if handler exists in the modular registry
@@ -2551,12 +2880,12 @@ async function handleTool(auth, name, args) {
2551
2880
  if (handler) {
2552
2881
  // Build handler context
2553
2882
  const ctx = {
2554
- supabase,
2555
2883
  auth,
2556
2884
  session: {
2557
2885
  instanceId: INSTANCE_ID,
2558
2886
  currentSessionId,
2559
2887
  currentPersona,
2888
+ currentRole,
2560
2889
  tokenUsage: sessionTokenUsage,
2561
2890
  },
2562
2891
  updateSession: (updates) => {
@@ -2564,6 +2893,8 @@ async function handleTool(auth, name, args) {
2564
2893
  currentSessionId = updates.currentSessionId;
2565
2894
  if (updates.currentPersona !== undefined)
2566
2895
  currentPersona = updates.currentPersona;
2896
+ if (updates.currentRole !== undefined)
2897
+ currentRole = updates.currentRole;
2567
2898
  if (updates.tokenUsage !== undefined)
2568
2899
  sessionTokenUsage = updates.tokenUsage;
2569
2900
  },
@@ -2577,8 +2908,8 @@ async function handleTool(auth, name, args) {
2577
2908
  // Server Setup
2578
2909
  // ============================================================================
2579
2910
  async function main() {
2580
- // Validate API key on startup
2581
- const auth = await validateApiKey(API_KEY);
2911
+ // Validate API key on startup via API
2912
+ const auth = await validateApiKey();
2582
2913
  if (!auth) {
2583
2914
  console.error('Invalid API key');
2584
2915
  process.exit(1);