@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/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', 'feature', 'bugfix', 'design', 'mcp', 'testing', 'docs', 'infra', 'other'],
1056
+ description: 'Task category (frontend, backend, database, feature, bugfix, design, 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', 'feature', 'bugfix', 'design', 'mcp', 'testing', 'docs', 'infra', 'other'],
1105
+ description: 'Task category (frontend, backend, database, feature, bugfix, design, 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.`,
@@ -1799,6 +1817,10 @@ Returns subtasks with aggregate completion stats.`,
1799
1817
  type: 'string',
1800
1818
  description: 'Session ID from start_work_session (optional, uses current session if not provided)',
1801
1819
  },
1820
+ current_worktree_path: {
1821
+ type: ['string', 'null'],
1822
+ description: 'Report your current git worktree path (e.g., "../project-task-abc123"). Set to null to clear.',
1823
+ },
1802
1824
  },
1803
1825
  },
1804
1826
  },
@@ -1848,7 +1870,7 @@ Returns subtasks with aggregate completion stats.`,
1848
1870
  },
1849
1871
  {
1850
1872
  name: 'validate_task',
1851
- description: 'Validate a completed task. Include test results in validation_notes.',
1873
+ 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).',
1852
1874
  inputSchema: {
1853
1875
  type: 'object',
1854
1876
  properties: {
@@ -1864,6 +1886,10 @@ Returns subtasks with aggregate completion stats.`,
1864
1886
  type: 'boolean',
1865
1887
  description: 'Whether the task passes validation (true = approved, false = needs more work)',
1866
1888
  },
1889
+ skip_pr_check: {
1890
+ type: 'boolean',
1891
+ description: 'Skip PR existence check (use only for tasks that legitimately do not need a PR)',
1892
+ },
1867
1893
  },
1868
1894
  required: ['task_id', 'approved'],
1869
1895
  },
@@ -2571,7 +2597,8 @@ Bodies of work allow organizing related tasks with optional auto-deployment on c
2571
2597
  },
2572
2598
  {
2573
2599
  name: 'get_body_of_work',
2574
- description: `Get a body of work with all its tasks organized by phase.`,
2600
+ description: `Get a body of work with all its tasks organized by phase.
2601
+ Use summary_only: true to get task counts and next task instead of full task arrays (saves tokens).`,
2575
2602
  inputSchema: {
2576
2603
  type: 'object',
2577
2604
  properties: {
@@ -2579,6 +2606,10 @@ Bodies of work allow organizing related tasks with optional auto-deployment on c
2579
2606
  type: 'string',
2580
2607
  description: 'Body of work UUID',
2581
2608
  },
2609
+ summary_only: {
2610
+ type: 'boolean',
2611
+ description: 'Return task counts and next task instead of full task arrays (default: false)',
2612
+ },
2582
2613
  },
2583
2614
  required: ['body_of_work_id'],
2584
2615
  },
@@ -2598,6 +2629,18 @@ Bodies of work allow organizing related tasks with optional auto-deployment on c
2598
2629
  enum: ['draft', 'active', 'completed', 'cancelled'],
2599
2630
  description: 'Filter by status (optional)',
2600
2631
  },
2632
+ limit: {
2633
+ type: 'number',
2634
+ description: 'Max number of bodies of work to return (default 50, max 100)',
2635
+ },
2636
+ offset: {
2637
+ type: 'number',
2638
+ description: 'Number of items to skip for pagination (default 0)',
2639
+ },
2640
+ search_query: {
2641
+ type: 'string',
2642
+ description: 'Search bodies of work by title or description',
2643
+ },
2601
2644
  },
2602
2645
  required: ['project_id'],
2603
2646
  },
@@ -2748,6 +2791,278 @@ Only returns tasks where all dependencies are completed.`,
2748
2791
  },
2749
2792
  },
2750
2793
  // ============================================================================
2794
+ // Sprint Tools
2795
+ // ============================================================================
2796
+ {
2797
+ name: 'create_sprint',
2798
+ description: `Create a new sprint. Sprints are time-bounded bodies of work with velocity tracking.
2799
+ Sprints start in 'planning' status where tasks can be added with story points.`,
2800
+ inputSchema: {
2801
+ type: 'object',
2802
+ properties: {
2803
+ project_id: {
2804
+ type: 'string',
2805
+ description: 'Project UUID',
2806
+ },
2807
+ title: {
2808
+ type: 'string',
2809
+ description: 'Sprint title (e.g., "Sprint 5" or "Q1 Release")',
2810
+ },
2811
+ goal: {
2812
+ type: 'string',
2813
+ description: 'Sprint goal statement',
2814
+ },
2815
+ start_date: {
2816
+ type: 'string',
2817
+ description: 'Start date (YYYY-MM-DD)',
2818
+ },
2819
+ end_date: {
2820
+ type: 'string',
2821
+ description: 'End date (YYYY-MM-DD)',
2822
+ },
2823
+ auto_deploy_on_completion: {
2824
+ type: 'boolean',
2825
+ description: 'Automatically request deployment when sprint completes (default: false)',
2826
+ },
2827
+ deploy_environment: {
2828
+ type: 'string',
2829
+ enum: ['development', 'staging', 'production'],
2830
+ description: 'Target environment for auto-deploy (default: production)',
2831
+ },
2832
+ deploy_version_bump: {
2833
+ type: 'string',
2834
+ enum: ['patch', 'minor', 'major'],
2835
+ description: 'Version bump for auto-deploy (default: minor)',
2836
+ },
2837
+ },
2838
+ required: ['project_id', 'title', 'start_date', 'end_date'],
2839
+ },
2840
+ },
2841
+ {
2842
+ name: 'update_sprint',
2843
+ description: `Update a sprint's details. Can update title, goal, dates, and deployment settings.`,
2844
+ inputSchema: {
2845
+ type: 'object',
2846
+ properties: {
2847
+ sprint_id: {
2848
+ type: 'string',
2849
+ description: 'Sprint UUID',
2850
+ },
2851
+ title: {
2852
+ type: 'string',
2853
+ description: 'New sprint title',
2854
+ },
2855
+ goal: {
2856
+ type: 'string',
2857
+ description: 'New sprint goal',
2858
+ },
2859
+ start_date: {
2860
+ type: 'string',
2861
+ description: 'New start date (YYYY-MM-DD)',
2862
+ },
2863
+ end_date: {
2864
+ type: 'string',
2865
+ description: 'New end date (YYYY-MM-DD)',
2866
+ },
2867
+ auto_deploy_on_completion: {
2868
+ type: 'boolean',
2869
+ description: 'Auto-deploy setting',
2870
+ },
2871
+ deploy_environment: {
2872
+ type: 'string',
2873
+ enum: ['development', 'staging', 'production'],
2874
+ description: 'Target environment',
2875
+ },
2876
+ deploy_version_bump: {
2877
+ type: 'string',
2878
+ enum: ['patch', 'minor', 'major'],
2879
+ description: 'Version bump type',
2880
+ },
2881
+ },
2882
+ required: ['sprint_id'],
2883
+ },
2884
+ },
2885
+ {
2886
+ name: 'get_sprint',
2887
+ description: `Get a sprint with all its tasks organized by phase (pre/core/post).
2888
+ Includes progress percentage, velocity points, and committed points.
2889
+ Use summary_only: true to get task counts and next task instead of full task arrays (saves tokens).`,
2890
+ inputSchema: {
2891
+ type: 'object',
2892
+ properties: {
2893
+ sprint_id: {
2894
+ type: 'string',
2895
+ description: 'Sprint UUID',
2896
+ },
2897
+ summary_only: {
2898
+ type: 'boolean',
2899
+ description: 'Return task counts and next task instead of full task arrays (default: false)',
2900
+ },
2901
+ },
2902
+ required: ['sprint_id'],
2903
+ },
2904
+ },
2905
+ {
2906
+ name: 'get_sprints',
2907
+ description: `List sprints for a project with velocity metrics.
2908
+ Returns sprints sorted by sprint_number descending (most recent first).`,
2909
+ inputSchema: {
2910
+ type: 'object',
2911
+ properties: {
2912
+ project_id: {
2913
+ type: 'string',
2914
+ description: 'Project UUID',
2915
+ },
2916
+ status: {
2917
+ type: 'string',
2918
+ enum: ['planning', 'active', 'in_review', 'retrospective', 'completed', 'cancelled'],
2919
+ description: 'Filter by sprint status (optional)',
2920
+ },
2921
+ limit: {
2922
+ type: 'number',
2923
+ description: 'Max sprints to return (default: 20, max: 100)',
2924
+ },
2925
+ },
2926
+ required: ['project_id'],
2927
+ },
2928
+ },
2929
+ {
2930
+ name: 'delete_sprint',
2931
+ description: `Delete a sprint. Tasks are preserved but no longer grouped.`,
2932
+ inputSchema: {
2933
+ type: 'object',
2934
+ properties: {
2935
+ sprint_id: {
2936
+ type: 'string',
2937
+ description: 'Sprint UUID',
2938
+ },
2939
+ },
2940
+ required: ['sprint_id'],
2941
+ },
2942
+ },
2943
+ {
2944
+ name: 'start_sprint',
2945
+ description: `Start a sprint. Transitions from 'planning' to 'active' status.
2946
+ Locks the committed_points at the current total story points.`,
2947
+ inputSchema: {
2948
+ type: 'object',
2949
+ properties: {
2950
+ sprint_id: {
2951
+ type: 'string',
2952
+ description: 'Sprint UUID',
2953
+ },
2954
+ },
2955
+ required: ['sprint_id'],
2956
+ },
2957
+ },
2958
+ {
2959
+ name: 'complete_sprint',
2960
+ description: `Complete a sprint. Handles retrospective phase and auto-deployment if configured.
2961
+ Status flow: active → in_review → retrospective → completed`,
2962
+ inputSchema: {
2963
+ type: 'object',
2964
+ properties: {
2965
+ sprint_id: {
2966
+ type: 'string',
2967
+ description: 'Sprint UUID',
2968
+ },
2969
+ retrospective_notes: {
2970
+ type: 'string',
2971
+ description: 'Sprint retrospective notes',
2972
+ },
2973
+ skip_retrospective: {
2974
+ type: 'boolean',
2975
+ description: 'Skip retrospective phase and go directly to completed (default: false)',
2976
+ },
2977
+ },
2978
+ required: ['sprint_id'],
2979
+ },
2980
+ },
2981
+ {
2982
+ name: 'add_task_to_sprint',
2983
+ description: `Add a task to a sprint with optional story points.
2984
+ Tasks can be added during 'planning' status. Story points contribute to committed_points.`,
2985
+ inputSchema: {
2986
+ type: 'object',
2987
+ properties: {
2988
+ sprint_id: {
2989
+ type: 'string',
2990
+ description: 'Sprint UUID',
2991
+ },
2992
+ task_id: {
2993
+ type: 'string',
2994
+ description: 'Task UUID to add',
2995
+ },
2996
+ story_points: {
2997
+ type: 'number',
2998
+ description: 'Story point estimate (optional, must be non-negative integer)',
2999
+ },
3000
+ phase: {
3001
+ type: 'string',
3002
+ enum: ['pre', 'core', 'post'],
3003
+ description: 'Task phase (default: core)',
3004
+ },
3005
+ },
3006
+ required: ['sprint_id', 'task_id'],
3007
+ },
3008
+ },
3009
+ {
3010
+ name: 'remove_task_from_sprint',
3011
+ description: `Remove a task from a sprint. Task is preserved but returns to backlog.`,
3012
+ inputSchema: {
3013
+ type: 'object',
3014
+ properties: {
3015
+ sprint_id: {
3016
+ type: 'string',
3017
+ description: 'Sprint UUID',
3018
+ },
3019
+ task_id: {
3020
+ type: 'string',
3021
+ description: 'Task UUID to remove',
3022
+ },
3023
+ },
3024
+ required: ['sprint_id', 'task_id'],
3025
+ },
3026
+ },
3027
+ {
3028
+ name: 'get_sprint_backlog',
3029
+ description: `Get tasks from backlog/pending that can be added to a sprint.
3030
+ Returns tasks not already assigned to any body of work or sprint.`,
3031
+ inputSchema: {
3032
+ type: 'object',
3033
+ properties: {
3034
+ project_id: {
3035
+ type: 'string',
3036
+ description: 'Project UUID',
3037
+ },
3038
+ sprint_id: {
3039
+ type: 'string',
3040
+ description: 'Sprint UUID to exclude already-added tasks (optional)',
3041
+ },
3042
+ },
3043
+ required: ['project_id'],
3044
+ },
3045
+ },
3046
+ {
3047
+ name: 'get_sprint_velocity',
3048
+ description: `Get velocity metrics for completed sprints.
3049
+ Returns committed vs completed points and average velocity.`,
3050
+ inputSchema: {
3051
+ type: 'object',
3052
+ properties: {
3053
+ project_id: {
3054
+ type: 'string',
3055
+ description: 'Project UUID',
3056
+ },
3057
+ limit: {
3058
+ type: 'number',
3059
+ description: 'Number of sprints to analyze (default: 10, max: 50)',
3060
+ },
3061
+ },
3062
+ required: ['project_id'],
3063
+ },
3064
+ },
3065
+ // ============================================================================
2751
3066
  // Organization Tools
2752
3067
  // ============================================================================
2753
3068
  {
@@ -3001,24 +3316,15 @@ async function handleTool(
3001
3316
  name: string,
3002
3317
  args: Record<string, unknown>
3003
3318
  ): Promise<{ result: unknown; user_updates?: UserUpdates; reminder?: string }> {
3004
- // Update session on every tool call:
3319
+ // Update session on every tool call via API:
3005
3320
  // - 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
3321
+ // - token tracking: synced periodically for cost monitoring
3008
3322
  // Skip for start_work_session since it handles its own session setup
3009
3323
  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
- }
3324
+ const apiClient = getApiClient();
3325
+ // Fire and forget - don't block tool execution on session sync
3326
+ apiClient.syncSession(currentSessionId).catch(() => {
3327
+ // Silently ignore sync errors - session tracking is non-critical
3022
3328
  });
3023
3329
  }
3024
3330
 
@@ -3027,17 +3333,18 @@ async function handleTool(
3027
3333
  if (handler) {
3028
3334
  // Build handler context
3029
3335
  const ctx: HandlerContext = {
3030
- supabase,
3031
3336
  auth,
3032
3337
  session: {
3033
3338
  instanceId: INSTANCE_ID,
3034
3339
  currentSessionId,
3035
3340
  currentPersona,
3341
+ currentRole,
3036
3342
  tokenUsage: sessionTokenUsage,
3037
3343
  },
3038
3344
  updateSession: (updates) => {
3039
3345
  if (updates.currentSessionId !== undefined) currentSessionId = updates.currentSessionId;
3040
3346
  if (updates.currentPersona !== undefined) currentPersona = updates.currentPersona;
3347
+ if (updates.currentRole !== undefined) currentRole = updates.currentRole;
3041
3348
  if (updates.tokenUsage !== undefined) sessionTokenUsage = updates.tokenUsage;
3042
3349
  },
3043
3350
  };
@@ -3054,8 +3361,8 @@ async function handleTool(
3054
3361
  // ============================================================================
3055
3362
 
3056
3363
  async function main() {
3057
- // Validate API key on startup
3058
- const auth = await validateApiKey(API_KEY!);
3364
+ // Validate API key on startup via API
3365
+ const auth = await validateApiKey();
3059
3366
  if (!auth) {
3060
3367
  console.error('Invalid API key');
3061
3368
  process.exit(1);