@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/index.ts CHANGED
@@ -7,8 +7,8 @@ import {
7
7
  ListToolsRequestSchema,
8
8
  type Tool,
9
9
  } from '@modelcontextprotocol/sdk/types.js';
10
- import { createClient } from '@supabase/supabase-js';
11
10
  import { randomUUID } from 'crypto';
11
+ import { initApiClient, getApiClient } from './api-client.js';
12
12
  import {
13
13
  ValidationError,
14
14
  validateRequired,
@@ -34,7 +34,6 @@ import {
34
34
  extractProjectNameFromGitUrl,
35
35
  isValidStatusTransition,
36
36
  } from './utils.js';
37
- import { hashApiKey } from './cli.js';
38
37
  import { buildHandlerRegistry, type HandlerContext } from './handlers/index.js';
39
38
 
40
39
  // ============================================================================
@@ -50,6 +49,9 @@ let currentSessionId: string | null = null;
50
49
  // Assigned persona for this agent instance
51
50
  let currentPersona: string | null = null;
52
51
 
52
+ // Current role for this agent instance
53
+ let currentRole: 'developer' | 'validator' | 'deployer' | 'reviewer' | 'maintainer' | null = null;
54
+
53
55
  // Token usage tracking for this session
54
56
  interface ModelTokens {
55
57
  input: number;
@@ -589,188 +591,35 @@ interface UserUpdates {
589
591
  // Configuration
590
592
  // ============================================================================
591
593
 
592
- // Default Supabase URL for Vibescope production instance
593
- const DEFAULT_SUPABASE_URL = 'https://uuneucmuubpgswvfijwd.supabase.co';
594
-
595
- const SUPABASE_URL = process.env.SUPABASE_URL || process.env.PUBLIC_SUPABASE_URL || DEFAULT_SUPABASE_URL;
596
- const SUPABASE_SERVICE_KEY = process.env.SUPABASE_SERVICE_KEY;
597
594
  const API_KEY = process.env.VIBESCOPE_API_KEY;
598
595
 
599
- if (!SUPABASE_SERVICE_KEY) {
600
- console.error('Missing required environment variable: SUPABASE_SERVICE_KEY');
601
- process.exit(1);
602
- }
603
-
604
596
  if (!API_KEY) {
605
597
  console.error('Missing required environment variable: VIBESCOPE_API_KEY');
606
598
  process.exit(1);
607
599
  }
608
600
 
609
- // ============================================================================
610
- // Database Client
611
- // ============================================================================
612
-
613
- // Using untyped client for MCP server - data validation happens at runtime
614
- const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_KEY);
601
+ // Initialize the API client
602
+ initApiClient({ apiKey: API_KEY });
615
603
 
616
604
  // ============================================================================
617
605
  // Authentication
618
606
  // ============================================================================
619
607
 
620
- async function validateApiKey(apiKey: string): Promise<AuthContext | null> {
621
- const keyHash = hashApiKey(apiKey);
622
-
623
- const { data, error } = await supabase
624
- .from('api_keys')
625
- .select('id, user_id, organization_id, scope, is_valid')
626
- .eq('key_hash', keyHash)
627
- .single();
628
-
629
- if (error || !data) {
630
- return null;
631
- }
608
+ async function validateApiKey(): Promise<AuthContext | null> {
609
+ const apiClient = getApiClient();
610
+ const response = await apiClient.validateAuth();
632
611
 
633
- // Check if key has been invalidated (e.g., user left org)
634
- if (data.is_valid === false) {
612
+ if (!response.ok || !response.data?.valid) {
635
613
  return null;
636
614
  }
637
615
 
638
- // For org-scoped keys, verify user is still a member
639
- if (data.scope === 'organization' && data.organization_id) {
640
- const { data: member } = await supabase
641
- .from('organization_members')
642
- .select('role')
643
- .eq('organization_id', data.organization_id)
644
- .eq('user_id', data.user_id)
645
- .single();
646
-
647
- if (!member) {
648
- // User is no longer a member, invalidate the key
649
- await supabase
650
- .from('api_keys')
651
- .update({ is_valid: false })
652
- .eq('id', data.id);
653
- return null;
654
- }
655
- }
656
-
657
- // Update last_used_at
658
- await supabase
659
- .from('api_keys')
660
- .update({ last_used_at: new Date().toISOString() })
661
- .eq('id', data.id);
662
-
663
616
  return {
664
- userId: data.user_id,
665
- apiKeyId: data.id,
666
- organizationId: data.organization_id || undefined,
667
- scope: data.scope || 'personal',
617
+ userId: response.data.user_id,
618
+ apiKeyId: response.data.api_key_id,
619
+ scope: 'personal', // API handles authorization, scope not needed locally
668
620
  };
669
621
  }
670
622
 
671
- // ============================================================================
672
- // Session Status Checking (Multi-Agent Coordination)
673
- // ============================================================================
674
-
675
- /**
676
- * Check if an agent session is active or stale.
677
- * A session is considered stale if:
678
- * - It doesn't exist (orphaned reference)
679
- * - Its status is 'disconnected'
680
- * - Its last_synced_at is older than 5 minutes
681
- */
682
- async function checkSessionStatus(
683
- sessionId: string
684
- ): Promise<{ exists: boolean; isActive: boolean; agentName?: string }> {
685
- const { data: session } = await supabase
686
- .from('agent_sessions')
687
- .select('id, status, last_synced_at, agent_name, instance_id')
688
- .eq('id', sessionId)
689
- .single();
690
-
691
- if (!session) {
692
- return { exists: false, isActive: false };
693
- }
694
-
695
- const lastSync = new Date(session.last_synced_at).getTime();
696
- const fiveMinutesAgo = Date.now() - 5 * 60 * 1000;
697
- const isActive = session.status !== 'disconnected' && lastSync > fiveMinutesAgo;
698
-
699
- return {
700
- exists: true,
701
- isActive,
702
- agentName: session.agent_name || `Agent ${session.instance_id?.slice(0, 8) || sessionId.slice(0, 8)}`,
703
- };
704
- }
705
-
706
- // ============================================================================
707
- // User Updates Tracking
708
- // ============================================================================
709
-
710
- async function getUserUpdates(
711
- auth: AuthContext,
712
- projectId: string
713
- ): Promise<UserUpdates | undefined> {
714
- // Get session's last_synced_at - prefer currentSessionId for accuracy
715
- let lastSyncedAt: string;
716
-
717
- if (currentSessionId) {
718
- const { data: session } = await supabase
719
- .from('agent_sessions')
720
- .select('last_synced_at')
721
- .eq('id', currentSessionId)
722
- .single();
723
- lastSyncedAt = session?.last_synced_at || new Date(0).toISOString();
724
- } else {
725
- const { data: session } = await supabase
726
- .from('agent_sessions')
727
- .select('last_synced_at')
728
- .eq('api_key_id', auth.apiKeyId)
729
- .eq('project_id', projectId)
730
- .single();
731
- lastSyncedAt = session?.last_synced_at || new Date(0).toISOString();
732
- }
733
-
734
- // Fetch user-created items since last sync (limit to 5 each for context efficiency)
735
- const [tasksResult, blockersResult, ideasResult] = await Promise.all([
736
- supabase
737
- .from('tasks')
738
- .select('id, title, created_at')
739
- .eq('project_id', projectId)
740
- .eq('created_by', 'user')
741
- .gt('created_at', lastSyncedAt)
742
- .order('created_at', { ascending: false })
743
- .limit(5),
744
- supabase
745
- .from('blockers')
746
- .select('id, description, created_at')
747
- .eq('project_id', projectId)
748
- .eq('created_by', 'user')
749
- .gt('created_at', lastSyncedAt)
750
- .order('created_at', { ascending: false })
751
- .limit(5),
752
- supabase
753
- .from('ideas')
754
- .select('id, title, created_at')
755
- .eq('project_id', projectId)
756
- .eq('created_by', 'user')
757
- .gt('created_at', lastSyncedAt)
758
- .order('created_at', { ascending: false })
759
- .limit(5),
760
- ]);
761
-
762
- const tasks = tasksResult.data || [];
763
- const blockers = blockersResult.data || [];
764
- const ideas = ideasResult.data || [];
765
-
766
- // Return undefined if no new updates (saves context window space)
767
- if (tasks.length === 0 && blockers.length === 0 && ideas.length === 0) {
768
- return undefined;
769
- }
770
-
771
- return { tasks, blockers, ideas };
772
- }
773
-
774
623
  // ============================================================================
775
624
  // Tool Definitions
776
625
  // ============================================================================
@@ -812,7 +661,7 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
812
661
  properties: {
813
662
  topic: {
814
663
  type: 'string',
815
- enum: ['getting_started', 'tasks', 'validation', 'deployment', 'git', 'blockers', 'milestones', 'fallback', 'session', 'tokens', 'topics'],
664
+ enum: ['getting_started', 'tasks', 'validation', 'deployment', 'git', 'blockers', 'milestones', 'fallback', 'session', 'tokens', 'sprints', 'topics'],
816
665
  description: 'Help topic. Use "topics" to list all available.',
817
666
  },
818
667
  },
@@ -1138,6 +987,14 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
1138
987
  type: 'number',
1139
988
  description: 'Max number of tasks to return (default 50)',
1140
989
  },
990
+ offset: {
991
+ type: 'number',
992
+ description: 'Number of tasks to skip for pagination (default 0)',
993
+ },
994
+ search_query: {
995
+ type: 'string',
996
+ description: 'Search tasks by title',
997
+ },
1141
998
  include_subtasks: {
1142
999
  type: 'boolean',
1143
1000
  description: 'Include subtasks in results (default false). Use get_subtasks for subtasks of a specific task.',
@@ -1193,6 +1050,11 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
1193
1050
  type: 'boolean',
1194
1051
  description: 'When true, this task blocks all other work until complete (used for deployment finalization)',
1195
1052
  },
1053
+ task_type: {
1054
+ type: 'string',
1055
+ enum: ['frontend', 'backend', 'database', 'mcp', 'testing', 'docs', 'infra', 'other'],
1056
+ description: 'Task category for visual grouping (frontend, backend, database, mcp, testing, docs, infra, other)',
1057
+ },
1196
1058
  },
1197
1059
  required: ['project_id', 'title'],
1198
1060
  },
@@ -1237,6 +1099,11 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
1237
1099
  type: 'string',
1238
1100
  description: 'Git branch associated with this task',
1239
1101
  },
1102
+ task_type: {
1103
+ type: 'string',
1104
+ enum: ['frontend', 'backend', 'database', 'mcp', 'testing', 'docs', 'infra', 'other'],
1105
+ description: 'Task category (frontend, backend, database, mcp, testing, docs, infra, other)',
1106
+ },
1240
1107
  },
1241
1108
  required: ['task_id'],
1242
1109
  },
@@ -1336,6 +1203,18 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
1336
1203
  enum: ['open', 'resolved'],
1337
1204
  description: 'Filter by status (default: open)',
1338
1205
  },
1206
+ limit: {
1207
+ type: 'number',
1208
+ description: 'Max number of blockers to return (default 50, max 200)',
1209
+ },
1210
+ offset: {
1211
+ type: 'number',
1212
+ description: 'Number of blockers to skip for pagination (default 0)',
1213
+ },
1214
+ search_query: {
1215
+ type: 'string',
1216
+ description: 'Search blockers by description',
1217
+ },
1339
1218
  },
1340
1219
  required: ['project_id'],
1341
1220
  },
@@ -1381,6 +1260,18 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
1381
1260
  type: 'string',
1382
1261
  description: 'Project UUID',
1383
1262
  },
1263
+ limit: {
1264
+ type: 'number',
1265
+ description: 'Max number of decisions to return (default 50, max 200)',
1266
+ },
1267
+ offset: {
1268
+ type: 'number',
1269
+ description: 'Number of decisions to skip for pagination (default 0)',
1270
+ },
1271
+ search_query: {
1272
+ type: 'string',
1273
+ description: 'Search decisions by title or description',
1274
+ },
1384
1275
  },
1385
1276
  required: ['project_id'],
1386
1277
  },
@@ -1458,6 +1349,18 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
1458
1349
  enum: ['raw', 'exploring', 'planned', 'in_development', 'shipped'],
1459
1350
  description: 'Filter by status (optional)',
1460
1351
  },
1352
+ limit: {
1353
+ type: 'number',
1354
+ description: 'Max number of ideas to return (default 50, max 100)',
1355
+ },
1356
+ offset: {
1357
+ type: 'number',
1358
+ description: 'Number of ideas to skip for pagination (default 0)',
1359
+ },
1360
+ search_query: {
1361
+ type: 'string',
1362
+ description: 'Search ideas by title or description',
1363
+ },
1461
1364
  },
1462
1365
  required: ['project_id'],
1463
1366
  },
@@ -1588,7 +1491,9 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
1588
1491
  category: { type: 'string', enum: ['performance', 'security', 'code_quality', 'accessibility', 'documentation', 'architecture', 'testing', 'other'], description: 'Filter by category (optional)' },
1589
1492
  severity: { type: 'string', enum: ['info', 'low', 'medium', 'high', 'critical'], description: 'Filter by severity (optional)' },
1590
1493
  status: { type: 'string', enum: ['open', 'addressed', 'dismissed', 'wontfix'], description: 'Filter by status (default: all)' },
1591
- limit: { type: 'number', description: 'Max number of findings to return (default 50)' },
1494
+ limit: { type: 'number', description: 'Max number of findings to return (default 50, max 200)' },
1495
+ offset: { type: 'number', description: 'Number of findings to skip for pagination (default 0)' },
1496
+ search_query: { type: 'string', description: 'Search findings by title or description' },
1592
1497
  },
1593
1498
  required: ['project_id'],
1594
1499
  },
@@ -1634,6 +1539,119 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
1634
1539
  required: ['blocker_id'],
1635
1540
  },
1636
1541
  },
1542
+ // Git Issues tools
1543
+ {
1544
+ name: 'add_git_issue',
1545
+ description: `Record a git-related issue (merge conflict, push failure, etc.). Auto-created by claim_validation when conflicts detected.`,
1546
+ inputSchema: {
1547
+ type: 'object',
1548
+ properties: {
1549
+ project_id: {
1550
+ type: 'string',
1551
+ description: 'Project UUID',
1552
+ },
1553
+ issue_type: {
1554
+ type: 'string',
1555
+ enum: ['merge_conflict', 'push_failed', 'rebase_needed', 'branch_diverged', 'pr_not_mergeable'],
1556
+ description: 'Type of git issue',
1557
+ },
1558
+ branch: {
1559
+ type: 'string',
1560
+ description: 'Branch where the issue occurred',
1561
+ },
1562
+ target_branch: {
1563
+ type: 'string',
1564
+ description: 'Target branch for merge/rebase (optional)',
1565
+ },
1566
+ pr_url: {
1567
+ type: 'string',
1568
+ description: 'Pull request URL if applicable (optional)',
1569
+ },
1570
+ conflicting_files: {
1571
+ type: 'array',
1572
+ items: { type: 'string' },
1573
+ description: 'List of files with conflicts (optional)',
1574
+ },
1575
+ error_message: {
1576
+ type: 'string',
1577
+ description: 'Error message from git operation (optional)',
1578
+ },
1579
+ task_id: {
1580
+ type: 'string',
1581
+ description: 'Related task UUID (optional)',
1582
+ },
1583
+ },
1584
+ required: ['project_id', 'issue_type', 'branch'],
1585
+ },
1586
+ },
1587
+ {
1588
+ name: 'resolve_git_issue',
1589
+ description: `Mark a git issue as resolved.`,
1590
+ inputSchema: {
1591
+ type: 'object',
1592
+ properties: {
1593
+ git_issue_id: {
1594
+ type: 'string',
1595
+ description: 'Git issue UUID',
1596
+ },
1597
+ resolution_note: {
1598
+ type: 'string',
1599
+ description: 'How the issue was resolved (optional)',
1600
+ },
1601
+ auto_resolved: {
1602
+ type: 'boolean',
1603
+ description: 'Whether this was auto-resolved (e.g., PR became mergeable)',
1604
+ },
1605
+ },
1606
+ required: ['git_issue_id'],
1607
+ },
1608
+ },
1609
+ {
1610
+ name: 'get_git_issues',
1611
+ description: `Get git issues for a project, optionally filtered by status, type, or branch.`,
1612
+ inputSchema: {
1613
+ type: 'object',
1614
+ properties: {
1615
+ project_id: {
1616
+ type: 'string',
1617
+ description: 'Project UUID',
1618
+ },
1619
+ status: {
1620
+ type: 'string',
1621
+ enum: ['open', 'resolved'],
1622
+ description: 'Filter by status (default: open)',
1623
+ },
1624
+ issue_type: {
1625
+ type: 'string',
1626
+ enum: ['merge_conflict', 'push_failed', 'rebase_needed', 'branch_diverged', 'pr_not_mergeable'],
1627
+ description: 'Filter by issue type (optional)',
1628
+ },
1629
+ branch: {
1630
+ type: 'string',
1631
+ description: 'Filter by branch (optional)',
1632
+ },
1633
+ limit: {
1634
+ type: 'number',
1635
+ description: 'Max issues to return (default: 50)',
1636
+ },
1637
+ },
1638
+ required: ['project_id'],
1639
+ },
1640
+ },
1641
+ {
1642
+ name: 'delete_git_issue',
1643
+ description: `Delete a git issue.`,
1644
+ inputSchema: {
1645
+ type: 'object',
1646
+ properties: {
1647
+ git_issue_id: {
1648
+ type: 'string',
1649
+ description: 'Git issue UUID',
1650
+ },
1651
+ },
1652
+ required: ['git_issue_id'],
1653
+ },
1654
+ },
1637
1655
  {
1638
1656
  name: 'delete_decision',
1639
1657
  description: `Delete a decision.`,
@@ -2598,6 +2616,18 @@ Bodies of work allow organizing related tasks with optional auto-deployment on c
2598
2616
  enum: ['draft', 'active', 'completed', 'cancelled'],
2599
2617
  description: 'Filter by status (optional)',
2600
2618
  },
2619
+ limit: {
2620
+ type: 'number',
2621
+ description: 'Max number of bodies of work to return (default 50, max 100)',
2622
+ },
2623
+ offset: {
2624
+ type: 'number',
2625
+ description: 'Number of items to skip for pagination (default 0)',
2626
+ },
2627
+ search_query: {
2628
+ type: 'string',
2629
+ description: 'Search bodies of work by title or description',
2630
+ },
2601
2631
  },
2602
2632
  required: ['project_id'],
2603
2633
  },
@@ -2748,6 +2778,273 @@ Only returns tasks where all dependencies are completed.`,
2748
2778
  },
2749
2779
  },
2750
2780
  // ============================================================================
2781
+ // Sprint Tools
2782
+ // ============================================================================
2783
+ {
2784
+ name: 'create_sprint',
2785
+ description: `Create a new sprint. Sprints are time-bounded bodies of work with velocity tracking.
2786
+ Sprints start in 'planning' status where tasks can be added with story points.`,
2787
+ inputSchema: {
2788
+ type: 'object',
2789
+ properties: {
2790
+ project_id: {
2791
+ type: 'string',
2792
+ description: 'Project UUID',
2793
+ },
2794
+ title: {
2795
+ type: 'string',
2796
+ description: 'Sprint title (e.g., "Sprint 5" or "Q1 Release")',
2797
+ },
2798
+ goal: {
2799
+ type: 'string',
2800
+ description: 'Sprint goal statement',
2801
+ },
2802
+ start_date: {
2803
+ type: 'string',
2804
+ description: 'Start date (YYYY-MM-DD)',
2805
+ },
2806
+ end_date: {
2807
+ type: 'string',
2808
+ description: 'End date (YYYY-MM-DD)',
2809
+ },
2810
+ auto_deploy_on_completion: {
2811
+ type: 'boolean',
2812
+ description: 'Automatically request deployment when sprint completes (default: false)',
2813
+ },
2814
+ deploy_environment: {
2815
+ type: 'string',
2816
+ enum: ['development', 'staging', 'production'],
2817
+ description: 'Target environment for auto-deploy (default: production)',
2818
+ },
2819
+ deploy_version_bump: {
2820
+ type: 'string',
2821
+ enum: ['patch', 'minor', 'major'],
2822
+ description: 'Version bump for auto-deploy (default: minor)',
2823
+ },
2824
+ },
2825
+ required: ['project_id', 'title', 'start_date', 'end_date'],
2826
+ },
2827
+ },
2828
+ {
2829
+ name: 'update_sprint',
2830
+ description: `Update a sprint's details. Can update title, goal, dates, and deployment settings.`,
2831
+ inputSchema: {
2832
+ type: 'object',
2833
+ properties: {
2834
+ sprint_id: {
2835
+ type: 'string',
2836
+ description: 'Sprint UUID',
2837
+ },
2838
+ title: {
2839
+ type: 'string',
2840
+ description: 'New sprint title',
2841
+ },
2842
+ goal: {
2843
+ type: 'string',
2844
+ description: 'New sprint goal',
2845
+ },
2846
+ start_date: {
2847
+ type: 'string',
2848
+ description: 'New start date (YYYY-MM-DD)',
2849
+ },
2850
+ end_date: {
2851
+ type: 'string',
2852
+ description: 'New end date (YYYY-MM-DD)',
2853
+ },
2854
+ auto_deploy_on_completion: {
2855
+ type: 'boolean',
2856
+ description: 'Auto-deploy setting',
2857
+ },
2858
+ deploy_environment: {
2859
+ type: 'string',
2860
+ enum: ['development', 'staging', 'production'],
2861
+ description: 'Target environment',
2862
+ },
2863
+ deploy_version_bump: {
2864
+ type: 'string',
2865
+ enum: ['patch', 'minor', 'major'],
2866
+ description: 'Version bump type',
2867
+ },
2868
+ },
2869
+ required: ['sprint_id'],
2870
+ },
2871
+ },
2872
+ {
2873
+ name: 'get_sprint',
2874
+ description: `Get a sprint with all its tasks organized by phase (pre/core/post).
2875
+ Includes progress percentage, velocity points, and committed points.`,
2876
+ inputSchema: {
2877
+ type: 'object',
2878
+ properties: {
2879
+ sprint_id: {
2880
+ type: 'string',
2881
+ description: 'Sprint UUID',
2882
+ },
2883
+ },
2884
+ required: ['sprint_id'],
2885
+ },
2886
+ },
2887
+ {
2888
+ name: 'get_sprints',
2889
+ description: `List sprints for a project with velocity metrics.
2890
+ Returns sprints sorted by sprint_number descending (most recent first).`,
2891
+ inputSchema: {
2892
+ type: 'object',
2893
+ properties: {
2894
+ project_id: {
2895
+ type: 'string',
2896
+ description: 'Project UUID',
2897
+ },
2898
+ status: {
2899
+ type: 'string',
2900
+ enum: ['planning', 'active', 'in_review', 'retrospective', 'completed', 'cancelled'],
2901
+ description: 'Filter by sprint status (optional)',
2902
+ },
2903
+ limit: {
2904
+ type: 'number',
2905
+ description: 'Max sprints to return (default: 20, max: 100)',
2906
+ },
2907
+ },
2908
+ required: ['project_id'],
2909
+ },
2910
+ },
2911
+ {
2912
+ name: 'delete_sprint',
2913
+ description: `Delete a sprint. Tasks are preserved but no longer grouped.`,
2914
+ inputSchema: {
2915
+ type: 'object',
2916
+ properties: {
2917
+ sprint_id: {
2918
+ type: 'string',
2919
+ description: 'Sprint UUID',
2920
+ },
2921
+ },
2922
+ required: ['sprint_id'],
2923
+ },
2924
+ },
2925
+ {
2926
+ name: 'start_sprint',
2927
+ description: `Start a sprint. Transitions from 'planning' to 'active' status.
2928
+ Locks the committed_points at the current total story points.`,
2929
+ inputSchema: {
2930
+ type: 'object',
2931
+ properties: {
2932
+ sprint_id: {
2933
+ type: 'string',
2934
+ description: 'Sprint UUID',
2935
+ },
2936
+ },
2937
+ required: ['sprint_id'],
2938
+ },
2939
+ },
2940
+ {
2941
+ name: 'complete_sprint',
2942
+ description: `Complete a sprint. Handles retrospective phase and auto-deployment if configured.
2943
+ Status flow: active → in_review → retrospective → completed`,
2944
+ inputSchema: {
2945
+ type: 'object',
2946
+ properties: {
2947
+ sprint_id: {
2948
+ type: 'string',
2949
+ description: 'Sprint UUID',
2950
+ },
2951
+ retrospective_notes: {
2952
+ type: 'string',
2953
+ description: 'Sprint retrospective notes',
2954
+ },
2955
+ skip_retrospective: {
2956
+ type: 'boolean',
2957
+ description: 'Skip retrospective phase and go directly to completed (default: false)',
2958
+ },
2959
+ },
2960
+ required: ['sprint_id'],
2961
+ },
2962
+ },
2963
+ {
2964
+ name: 'add_task_to_sprint',
2965
+ description: `Add a task to a sprint with optional story points.
2966
+ Tasks can be added during 'planning' status. Story points contribute to committed_points.`,
2967
+ inputSchema: {
2968
+ type: 'object',
2969
+ properties: {
2970
+ sprint_id: {
2971
+ type: 'string',
2972
+ description: 'Sprint UUID',
2973
+ },
2974
+ task_id: {
2975
+ type: 'string',
2976
+ description: 'Task UUID to add',
2977
+ },
2978
+ story_points: {
2979
+ type: 'number',
2980
+ description: 'Story point estimate (optional, must be non-negative integer)',
2981
+ },
2982
+ phase: {
2983
+ type: 'string',
2984
+ enum: ['pre', 'core', 'post'],
2985
+ description: 'Task phase (default: core)',
2986
+ },
2987
+ },
2988
+ required: ['sprint_id', 'task_id'],
2989
+ },
2990
+ },
2991
+ {
2992
+ name: 'remove_task_from_sprint',
2993
+ description: `Remove a task from a sprint. Task is preserved but returns to backlog.`,
2994
+ inputSchema: {
2995
+ type: 'object',
2996
+ properties: {
2997
+ sprint_id: {
2998
+ type: 'string',
2999
+ description: 'Sprint UUID',
3000
+ },
3001
+ task_id: {
3002
+ type: 'string',
3003
+ description: 'Task UUID to remove',
3004
+ },
3005
+ },
3006
+ required: ['sprint_id', 'task_id'],
3007
+ },
3008
+ },
3009
+ {
3010
+ name: 'get_sprint_backlog',
3011
+ description: `Get tasks from backlog/pending that can be added to a sprint.
3012
+ Returns tasks not already assigned to any body of work or sprint.`,
3013
+ inputSchema: {
3014
+ type: 'object',
3015
+ properties: {
3016
+ project_id: {
3017
+ type: 'string',
3018
+ description: 'Project UUID',
3019
+ },
3020
+ sprint_id: {
3021
+ type: 'string',
3022
+ description: 'Sprint UUID to exclude already-added tasks (optional)',
3023
+ },
3024
+ },
3025
+ required: ['project_id'],
3026
+ },
3027
+ },
3028
+ {
3029
+ name: 'get_sprint_velocity',
3030
+ description: `Get velocity metrics for completed sprints.
3031
+ Returns committed vs completed points and average velocity.`,
3032
+ inputSchema: {
3033
+ type: 'object',
3034
+ properties: {
3035
+ project_id: {
3036
+ type: 'string',
3037
+ description: 'Project UUID',
3038
+ },
3039
+ limit: {
3040
+ type: 'number',
3041
+ description: 'Number of sprints to analyze (default: 10, max: 50)',
3042
+ },
3043
+ },
3044
+ required: ['project_id'],
3045
+ },
3046
+ },
3047
+ // ============================================================================
2751
3048
  // Organization Tools
2752
3049
  // ============================================================================
2753
3050
  {
@@ -3001,24 +3298,15 @@ async function handleTool(
3001
3298
  name: string,
3002
3299
  args: Record<string, unknown>
3003
3300
  ): Promise<{ result: unknown; user_updates?: UserUpdates; reminder?: string }> {
3004
- // Update session on every tool call:
3301
+ // Update session on every tool call via API:
3005
3302
  // - last_synced_at: keeps session alive and visible as "active" on dashboard
3006
- // - tool_call_count: incremented for token efficiency tracking
3007
- // - last_tool_at: timestamp of last tool call
3303
+ // - token tracking: synced periodically for cost monitoring
3008
3304
  // Skip for start_work_session since it handles its own session setup
3009
3305
  if (currentSessionId && name !== 'start_work_session') {
3010
- const now = new Date().toISOString();
3011
- await supabase.rpc('increment_tool_call_count', {
3012
- p_session_id: currentSessionId,
3013
- p_timestamp: now
3014
- }).then(({ error }) => {
3015
- // Fallback to direct update if RPC doesn't exist (pre-migration)
3016
- if (error) {
3017
- return supabase
3018
- .from('agent_sessions')
3019
- .update({ last_synced_at: now })
3020
- .eq('id', currentSessionId);
3021
- }
3306
+ const apiClient = getApiClient();
3307
+ // Fire and forget - don't block tool execution on session sync
3308
+ apiClient.syncSession(currentSessionId).catch(() => {
3309
+ // Silently ignore sync errors - session tracking is non-critical
3022
3310
  });
3023
3311
  }
3024
3312
 
@@ -3027,17 +3315,18 @@ async function handleTool(
3027
3315
  if (handler) {
3028
3316
  // Build handler context
3029
3317
  const ctx: HandlerContext = {
3030
- supabase,
3031
3318
  auth,
3032
3319
  session: {
3033
3320
  instanceId: INSTANCE_ID,
3034
3321
  currentSessionId,
3035
3322
  currentPersona,
3323
+ currentRole,
3036
3324
  tokenUsage: sessionTokenUsage,
3037
3325
  },
3038
3326
  updateSession: (updates) => {
3039
3327
  if (updates.currentSessionId !== undefined) currentSessionId = updates.currentSessionId;
3040
3328
  if (updates.currentPersona !== undefined) currentPersona = updates.currentPersona;
3329
+ if (updates.currentRole !== undefined) currentRole = updates.currentRole;
3041
3330
  if (updates.tokenUsage !== undefined) sessionTokenUsage = updates.tokenUsage;
3042
3331
  },
3043
3332
  };
@@ -3054,8 +3343,8 @@ async function handleTool(
3054
3343
  // ============================================================================
3055
3344
 
3056
3345
  async function main() {
3057
- // Validate API key on startup
3058
- const auth = await validateApiKey(API_KEY!);
3346
+ // Validate API key on startup via API
3347
+ const auth = await validateApiKey();
3059
3348
  if (!auth) {
3060
3349
  console.error('Invalid API key');
3061
3350
  process.exit(1);