@vibescope/mcp-server 0.2.0 → 0.2.2

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 (104) hide show
  1. package/README.md +60 -7
  2. package/dist/api-client.d.ts +251 -1
  3. package/dist/api-client.js +82 -3
  4. package/dist/handlers/blockers.js +9 -8
  5. package/dist/handlers/bodies-of-work.js +96 -63
  6. package/dist/handlers/connectors.d.ts +45 -0
  7. package/dist/handlers/connectors.js +183 -0
  8. package/dist/handlers/cost.d.ts +10 -0
  9. package/dist/handlers/cost.js +112 -50
  10. package/dist/handlers/decisions.js +32 -19
  11. package/dist/handlers/deployment.js +144 -122
  12. package/dist/handlers/discovery.d.ts +7 -0
  13. package/dist/handlers/discovery.js +96 -7
  14. package/dist/handlers/fallback.js +29 -23
  15. package/dist/handlers/file-checkouts.d.ts +20 -0
  16. package/dist/handlers/file-checkouts.js +133 -0
  17. package/dist/handlers/findings.d.ts +6 -0
  18. package/dist/handlers/findings.js +96 -40
  19. package/dist/handlers/git-issues.js +40 -36
  20. package/dist/handlers/ideas.js +49 -31
  21. package/dist/handlers/index.d.ts +3 -0
  22. package/dist/handlers/index.js +9 -0
  23. package/dist/handlers/milestones.js +39 -32
  24. package/dist/handlers/organizations.js +99 -91
  25. package/dist/handlers/progress.js +24 -13
  26. package/dist/handlers/project.js +68 -28
  27. package/dist/handlers/requests.js +18 -14
  28. package/dist/handlers/roles.d.ts +18 -0
  29. package/dist/handlers/roles.js +130 -0
  30. package/dist/handlers/session.js +58 -17
  31. package/dist/handlers/sprints.js +93 -81
  32. package/dist/handlers/tasks.d.ts +2 -0
  33. package/dist/handlers/tasks.js +189 -91
  34. package/dist/handlers/types.d.ts +64 -2
  35. package/dist/handlers/types.js +48 -1
  36. package/dist/handlers/validation.js +21 -17
  37. package/dist/index.js +7 -2716
  38. package/dist/token-tracking.d.ts +74 -0
  39. package/dist/token-tracking.js +122 -0
  40. package/dist/tools.js +685 -9
  41. package/dist/utils.d.ts +5 -0
  42. package/dist/utils.js +17 -0
  43. package/docs/TOOLS.md +2053 -0
  44. package/package.json +4 -1
  45. package/scripts/generate-docs.ts +212 -0
  46. package/src/api-client.test.ts +718 -0
  47. package/src/api-client.ts +320 -6
  48. package/src/handlers/__test-setup__.ts +16 -0
  49. package/src/handlers/blockers.test.ts +31 -19
  50. package/src/handlers/blockers.ts +9 -8
  51. package/src/handlers/bodies-of-work.test.ts +55 -32
  52. package/src/handlers/bodies-of-work.ts +115 -115
  53. package/src/handlers/connectors.test.ts +834 -0
  54. package/src/handlers/connectors.ts +229 -0
  55. package/src/handlers/cost.test.ts +34 -44
  56. package/src/handlers/cost.ts +136 -85
  57. package/src/handlers/decisions.test.ts +37 -27
  58. package/src/handlers/decisions.ts +35 -30
  59. package/src/handlers/deployment.ts +180 -208
  60. package/src/handlers/discovery.test.ts +4 -5
  61. package/src/handlers/discovery.ts +98 -8
  62. package/src/handlers/fallback.test.ts +26 -22
  63. package/src/handlers/fallback.ts +36 -33
  64. package/src/handlers/file-checkouts.test.ts +670 -0
  65. package/src/handlers/file-checkouts.ts +165 -0
  66. package/src/handlers/findings.test.ts +178 -19
  67. package/src/handlers/findings.ts +112 -74
  68. package/src/handlers/git-issues.test.ts +51 -43
  69. package/src/handlers/git-issues.ts +44 -84
  70. package/src/handlers/ideas.test.ts +28 -23
  71. package/src/handlers/ideas.ts +61 -59
  72. package/src/handlers/index.ts +9 -0
  73. package/src/handlers/milestones.test.ts +33 -28
  74. package/src/handlers/milestones.ts +52 -50
  75. package/src/handlers/organizations.test.ts +104 -83
  76. package/src/handlers/organizations.ts +117 -142
  77. package/src/handlers/progress.test.ts +20 -14
  78. package/src/handlers/progress.ts +26 -24
  79. package/src/handlers/project.test.ts +34 -27
  80. package/src/handlers/project.ts +95 -63
  81. package/src/handlers/requests.test.ts +27 -18
  82. package/src/handlers/requests.ts +21 -17
  83. package/src/handlers/roles.test.ts +303 -0
  84. package/src/handlers/roles.ts +208 -0
  85. package/src/handlers/session.test.ts +47 -0
  86. package/src/handlers/session.ts +71 -26
  87. package/src/handlers/sprints.test.ts +71 -50
  88. package/src/handlers/sprints.ts +113 -146
  89. package/src/handlers/tasks.test.ts +77 -15
  90. package/src/handlers/tasks.ts +231 -156
  91. package/src/handlers/tool-categories.test.ts +66 -0
  92. package/src/handlers/types.ts +81 -2
  93. package/src/handlers/validation.test.ts +78 -45
  94. package/src/handlers/validation.ts +23 -25
  95. package/src/index.ts +12 -2732
  96. package/src/token-tracking.test.ts +453 -0
  97. package/src/token-tracking.ts +164 -0
  98. package/src/tools.ts +685 -9
  99. package/src/utils.test.ts +2 -2
  100. package/src/utils.ts +17 -0
  101. package/dist/config/tool-categories.d.ts +0 -31
  102. package/dist/config/tool-categories.js +0 -253
  103. package/dist/knowledge.d.ts +0 -6
  104. package/dist/knowledge.js +0 -218
package/src/api-client.ts CHANGED
@@ -47,7 +47,8 @@ export class VibescopeApiClient {
47
47
  return {
48
48
  ok: false,
49
49
  status: response.status,
50
- error: data.error || `HTTP ${response.status}`
50
+ error: data.error || `HTTP ${response.status}`,
51
+ data // Include full response data for additional error context
51
52
  };
52
53
  }
53
54
 
@@ -122,6 +123,23 @@ export class VibescopeApiClient {
122
123
  priority: number;
123
124
  estimated_minutes?: number;
124
125
  } | null;
126
+ pending_requests?: Array<{
127
+ id: string;
128
+ request_type: string;
129
+ message: string;
130
+ created_at: string;
131
+ }>;
132
+ pending_requests_count?: number;
133
+ URGENT_QUESTIONS?: {
134
+ count: number;
135
+ oldest_waiting_minutes: number;
136
+ action_required: string;
137
+ requests: Array<{
138
+ id: string;
139
+ message: string;
140
+ waiting_minutes: number;
141
+ }>;
142
+ };
125
143
  directive?: string;
126
144
  blockers_count?: number;
127
145
  validation_count?: number;
@@ -306,6 +324,7 @@ export class VibescopeApiClient {
306
324
  estimated_minutes?: number;
307
325
  blocking?: boolean;
308
326
  session_id?: string;
327
+ task_type?: string;
309
328
  }): Promise<ApiResponse<{
310
329
  success: boolean;
311
330
  task_id: string;
@@ -380,6 +399,7 @@ export class VibescopeApiClient {
380
399
  progress_note?: string;
381
400
  estimated_minutes?: number;
382
401
  git_branch?: string;
402
+ worktree_path?: string;
383
403
  session_id?: string;
384
404
  }): Promise<ApiResponse<{
385
405
  success: boolean;
@@ -539,7 +559,11 @@ export class VibescopeApiClient {
539
559
  // ============================================================================
540
560
  // Decision endpoints
541
561
  // ============================================================================
542
- async getDecisions(projectId: string): Promise<ApiResponse<{
562
+ async getDecisions(projectId: string, options?: {
563
+ limit?: number;
564
+ offset?: number;
565
+ search_query?: string;
566
+ }): Promise<ApiResponse<{
543
567
  decisions: Array<{
544
568
  id: string;
545
569
  title: string;
@@ -549,7 +573,7 @@ export class VibescopeApiClient {
549
573
  created_at: string;
550
574
  }>;
551
575
  }>> {
552
- return this.proxy('get_decisions', { project_id: projectId });
576
+ return this.proxy('get_decisions', { project_id: projectId, ...options });
553
577
  }
554
578
 
555
579
  async logDecision(projectId: string, params: {
@@ -1005,6 +1029,8 @@ export class VibescopeApiClient {
1005
1029
  async getActivityFeed(projectId: string, params?: {
1006
1030
  limit?: number;
1007
1031
  since?: string;
1032
+ types?: string[];
1033
+ created_by?: string;
1008
1034
  }): Promise<ApiResponse<{
1009
1035
  activities: Array<{
1010
1036
  type: string;
@@ -1139,14 +1165,17 @@ export class VibescopeApiClient {
1139
1165
  // Knowledge base endpoint
1140
1166
  // ============================================================================
1141
1167
  async queryKnowledgeBase(projectId: string, params?: {
1168
+ scope?: 'summary' | 'detailed';
1142
1169
  categories?: string[];
1143
1170
  limit?: number;
1144
1171
  search_query?: string;
1145
1172
  }): Promise<ApiResponse<{
1146
- findings?: Array<{ id: string; title: string; category: string; severity: string }>;
1147
- decisions?: Array<{ id: string; title: string; description: string }>;
1148
- completed_tasks?: Array<{ id: string; title: string; completed_at: string }>;
1173
+ findings?: Array<{ id: string; title: string; category: string; severity: string; description?: string }>;
1174
+ decisions?: Array<{ id: string; title: string; description: string; rationale?: string }>;
1175
+ completed_tasks?: Array<{ id: string; title: string; completed_at: string; summary?: string }>;
1149
1176
  resolved_blockers?: Array<{ id: string; description: string; resolution_note?: string }>;
1177
+ progress?: Array<{ id: string; summary: string; details?: string }>;
1178
+ qa?: Array<{ id: string; question: string; answer: string }>;
1150
1179
  }>> {
1151
1180
  return this.proxy('query_knowledge_base', {
1152
1181
  project_id: projectId,
@@ -1598,6 +1627,63 @@ export class VibescopeApiClient {
1598
1627
  });
1599
1628
  }
1600
1629
 
1630
+ async getBodyOfWorkCosts(params: {
1631
+ body_of_work_id?: string;
1632
+ project_id?: string;
1633
+ }): Promise<ApiResponse<{
1634
+ bodies_of_work: Array<{
1635
+ body_of_work_id: string;
1636
+ title: string;
1637
+ project_id: string;
1638
+ status: string;
1639
+ task_count: number;
1640
+ total_cost_usd: number;
1641
+ total_tokens: number;
1642
+ pre_phase_cost_usd: number;
1643
+ core_phase_cost_usd: number;
1644
+ post_phase_cost_usd: number;
1645
+ model_breakdown: Record<string, { input: number; output: number }>;
1646
+ }>;
1647
+ count: number;
1648
+ totals: {
1649
+ total_cost_usd: number;
1650
+ total_tokens: number;
1651
+ total_tasks: number;
1652
+ };
1653
+ }>> {
1654
+ return this.proxy('get_body_of_work_costs', params);
1655
+ }
1656
+
1657
+ async getSprintCosts(params: {
1658
+ sprint_id?: string;
1659
+ project_id?: string;
1660
+ }): Promise<ApiResponse<{
1661
+ sprints: Array<{
1662
+ sprint_id: string;
1663
+ title: string;
1664
+ project_id: string;
1665
+ status: string;
1666
+ sprint_number: number;
1667
+ task_count: number;
1668
+ total_cost_usd: number;
1669
+ total_tokens: number;
1670
+ cost_per_story_point: number | null;
1671
+ committed_points: number;
1672
+ velocity_points: number;
1673
+ model_breakdown: Record<string, { input: number; output: number }>;
1674
+ }>;
1675
+ count: number;
1676
+ totals: {
1677
+ total_cost_usd: number;
1678
+ total_tokens: number;
1679
+ total_tasks: number;
1680
+ total_velocity_points: number;
1681
+ avg_cost_per_point: number | null;
1682
+ };
1683
+ }>> {
1684
+ return this.proxy('get_sprint_costs', params);
1685
+ }
1686
+
1601
1687
  async getTokenUsage(): Promise<ApiResponse<{
1602
1688
  session_tokens: number;
1603
1689
  estimated_cost: number;
@@ -1684,6 +1770,7 @@ export class VibescopeApiClient {
1684
1770
  environment?: string;
1685
1771
  version_bump?: string;
1686
1772
  auto_trigger?: boolean;
1773
+ hours_interval?: number;
1687
1774
  notes?: string;
1688
1775
  git_ref?: string;
1689
1776
  }): Promise<ApiResponse<{
@@ -1701,6 +1788,7 @@ export class VibescopeApiClient {
1701
1788
  id: string;
1702
1789
  scheduled_at: string;
1703
1790
  schedule_type: string;
1791
+ hours_interval: number;
1704
1792
  environment: string;
1705
1793
  version_bump: string;
1706
1794
  auto_trigger: boolean;
@@ -1720,6 +1808,7 @@ export class VibescopeApiClient {
1720
1808
  async updateScheduledDeployment(scheduleId: string, updates: {
1721
1809
  scheduled_at?: string;
1722
1810
  schedule_type?: string;
1811
+ hours_interval?: number;
1723
1812
  environment?: string;
1724
1813
  version_bump?: string;
1725
1814
  auto_trigger?: boolean;
@@ -1800,6 +1889,231 @@ export class VibescopeApiClient {
1800
1889
  }>>> {
1801
1890
  return this.proxy('get_help_topics', {});
1802
1891
  }
1892
+
1893
+ // ============================================================================
1894
+ // File Checkout endpoints (multi-agent coordination)
1895
+ // ============================================================================
1896
+ async checkoutFile(projectId: string, filePath: string, reason?: string, sessionId?: string): Promise<ApiResponse<{
1897
+ success: boolean;
1898
+ checkout_id: string;
1899
+ file_path: string;
1900
+ already_checked_out?: boolean;
1901
+ message: string;
1902
+ }>> {
1903
+ return this.proxy('checkout_file', {
1904
+ project_id: projectId,
1905
+ file_path: filePath,
1906
+ reason
1907
+ }, sessionId ? {
1908
+ session_id: sessionId,
1909
+ persona: null,
1910
+ instance_id: ''
1911
+ } : undefined);
1912
+ }
1913
+
1914
+ async checkinFile(params: {
1915
+ checkout_id?: string;
1916
+ project_id?: string;
1917
+ file_path?: string;
1918
+ summary?: string;
1919
+ }, sessionId?: string): Promise<ApiResponse<{
1920
+ success: boolean;
1921
+ checkout_id: string;
1922
+ message: string;
1923
+ }>> {
1924
+ return this.proxy('checkin_file', params, sessionId ? {
1925
+ session_id: sessionId,
1926
+ persona: null,
1927
+ instance_id: ''
1928
+ } : undefined);
1929
+ }
1930
+
1931
+ async getFileCheckouts(projectId: string, options?: {
1932
+ status?: string;
1933
+ file_path?: string;
1934
+ limit?: number;
1935
+ }): Promise<ApiResponse<{
1936
+ checkouts: Array<{
1937
+ id: string;
1938
+ file_path: string;
1939
+ status: string;
1940
+ checked_out_at: string;
1941
+ checkout_reason?: string;
1942
+ checked_in_at?: string;
1943
+ checkin_summary?: string;
1944
+ checked_out_by?: string;
1945
+ }>;
1946
+ }>> {
1947
+ return this.proxy('get_file_checkouts', {
1948
+ project_id: projectId,
1949
+ ...options
1950
+ });
1951
+ }
1952
+
1953
+ async abandonCheckout(params: {
1954
+ checkout_id?: string;
1955
+ project_id?: string;
1956
+ file_path?: string;
1957
+ }): Promise<ApiResponse<{
1958
+ success: boolean;
1959
+ checkout_id: string;
1960
+ message: string;
1961
+ }>> {
1962
+ return this.proxy('abandon_checkout', params);
1963
+ }
1964
+
1965
+ // ============================================================================
1966
+ // Worktree Management
1967
+ // ============================================================================
1968
+
1969
+ async getStaleWorktrees(projectId: string): Promise<ApiResponse<{
1970
+ project_id: string;
1971
+ project_name: string;
1972
+ stale_worktrees: Array<{
1973
+ task_id: string;
1974
+ task_title: string;
1975
+ worktree_path: string;
1976
+ git_branch: string | null;
1977
+ status: string;
1978
+ completed_at: string | null;
1979
+ updated_at: string;
1980
+ pr_url: string | null;
1981
+ stale_reason: 'task_finished' | 'potentially_abandoned';
1982
+ }>;
1983
+ count: number;
1984
+ cleanup_instructions: string[] | null;
1985
+ }>> {
1986
+ return this.request('GET', `/api/mcp/worktrees/stale?project_id=${projectId}`);
1987
+ }
1988
+
1989
+ async clearWorktreePath(taskId: string): Promise<ApiResponse<{
1990
+ success: boolean;
1991
+ task_id: string;
1992
+ }>> {
1993
+ return this.request('PATCH', `/api/mcp/tasks/${taskId}`, { worktree_path: null });
1994
+ }
1995
+
1996
+ // ============================================================================
1997
+ // Connector endpoints
1998
+ // ============================================================================
1999
+
2000
+ async getConnectors(projectId: string, params?: {
2001
+ type?: string;
2002
+ status?: string;
2003
+ limit?: number;
2004
+ offset?: number;
2005
+ }): Promise<ApiResponse<{
2006
+ connectors: Array<{
2007
+ id: string;
2008
+ name: string;
2009
+ type: string;
2010
+ description?: string;
2011
+ status: string;
2012
+ events: Record<string, boolean>;
2013
+ events_sent: number;
2014
+ last_triggered_at?: string;
2015
+ last_error?: string;
2016
+ last_error_at?: string;
2017
+ created_at: string;
2018
+ }>;
2019
+ total_count: number;
2020
+ has_more: boolean;
2021
+ }>> {
2022
+ return this.proxy('get_connectors', {
2023
+ project_id: projectId,
2024
+ ...params
2025
+ });
2026
+ }
2027
+
2028
+ async getConnector(connectorId: string): Promise<ApiResponse<{
2029
+ connector: {
2030
+ id: string;
2031
+ name: string;
2032
+ type: string;
2033
+ description?: string;
2034
+ config: Record<string, unknown>;
2035
+ events: Record<string, boolean>;
2036
+ status: string;
2037
+ events_sent: number;
2038
+ last_triggered_at?: string;
2039
+ last_error?: string;
2040
+ last_error_at?: string;
2041
+ created_at: string;
2042
+ };
2043
+ }>> {
2044
+ return this.proxy('get_connector', { connector_id: connectorId });
2045
+ }
2046
+
2047
+ async addConnector(projectId: string, params: {
2048
+ name: string;
2049
+ type: string;
2050
+ description?: string;
2051
+ config?: Record<string, unknown>;
2052
+ events?: Record<string, boolean>;
2053
+ }): Promise<ApiResponse<{
2054
+ success: boolean;
2055
+ connector_id: string;
2056
+ }>> {
2057
+ return this.proxy('add_connector', {
2058
+ project_id: projectId,
2059
+ ...params
2060
+ });
2061
+ }
2062
+
2063
+ async updateConnector(connectorId: string, updates: {
2064
+ name?: string;
2065
+ description?: string;
2066
+ config?: Record<string, unknown>;
2067
+ events?: Record<string, boolean>;
2068
+ status?: string;
2069
+ }): Promise<ApiResponse<{
2070
+ success: boolean;
2071
+ connector_id: string;
2072
+ }>> {
2073
+ return this.proxy('update_connector', {
2074
+ connector_id: connectorId,
2075
+ ...updates
2076
+ });
2077
+ }
2078
+
2079
+ async deleteConnector(connectorId: string): Promise<ApiResponse<{
2080
+ success: boolean;
2081
+ }>> {
2082
+ return this.proxy('delete_connector', { connector_id: connectorId });
2083
+ }
2084
+
2085
+ async testConnector(connectorId: string): Promise<ApiResponse<{
2086
+ success: boolean;
2087
+ event_id: string;
2088
+ status?: number;
2089
+ error?: string;
2090
+ }>> {
2091
+ return this.proxy('test_connector', { connector_id: connectorId });
2092
+ }
2093
+
2094
+ async getConnectorEvents(params: {
2095
+ connector_id?: string;
2096
+ project_id?: string;
2097
+ status?: string;
2098
+ limit?: number;
2099
+ offset?: number;
2100
+ }): Promise<ApiResponse<{
2101
+ events: Array<{
2102
+ id: string;
2103
+ connector_id: string;
2104
+ event_type: string;
2105
+ status: string;
2106
+ response_status?: number;
2107
+ error_message?: string;
2108
+ attempts: number;
2109
+ created_at: string;
2110
+ sent_at?: string;
2111
+ }>;
2112
+ total_count: number;
2113
+ has_more: boolean;
2114
+ }>> {
2115
+ return this.proxy('get_connector_events', params);
2116
+ }
1803
2117
  }
1804
2118
 
1805
2119
  // Singleton instance
@@ -80,6 +80,7 @@ export const mockApiClient = {
80
80
  getFindingsStats: vi.fn(),
81
81
  updateFinding: vi.fn(),
82
82
  deleteFinding: vi.fn(),
83
+ queryKnowledgeBase: vi.fn(),
83
84
 
84
85
  // Requests
85
86
  getPendingRequests: vi.fn(),
@@ -169,6 +170,21 @@ export const mockApiClient = {
169
170
  getHelpTopic: vi.fn(),
170
171
  getHelpTopics: vi.fn(),
171
172
 
173
+ // File Checkouts
174
+ checkoutFile: vi.fn(),
175
+ checkinFile: vi.fn(),
176
+ getFileCheckouts: vi.fn(),
177
+ abandonCheckout: vi.fn(),
178
+
179
+ // Connectors
180
+ getConnectors: vi.fn(),
181
+ getConnector: vi.fn(),
182
+ addConnector: vi.fn(),
183
+ updateConnector: vi.fn(),
184
+ deleteConnector: vi.fn(),
185
+ testConnector: vi.fn(),
186
+ getConnectorEvents: vi.fn(),
187
+
172
188
  // Proxy (generic)
173
189
  proxy: vi.fn(),
174
190
  };
@@ -83,19 +83,22 @@ describe('addBlocker', () => {
83
83
  );
84
84
  });
85
85
 
86
- it('should throw error when API call fails', async () => {
86
+ it('should return error when API call fails', async () => {
87
87
  mockApiClient.addBlocker.mockResolvedValue({
88
88
  ok: false,
89
89
  error: 'Insert failed',
90
90
  });
91
91
  const ctx = createMockContext();
92
92
 
93
- await expect(
94
- addBlocker({
95
- project_id: '123e4567-e89b-12d3-a456-426614174000',
96
- description: 'Test blocker',
97
- }, ctx)
98
- ).rejects.toThrow('Insert failed');
93
+ const result = await addBlocker({
94
+ project_id: '123e4567-e89b-12d3-a456-426614174000',
95
+ description: 'Test blocker',
96
+ }, ctx);
97
+
98
+ expect(result.isError).toBe(true);
99
+ expect(result.result).toMatchObject({
100
+ error: 'Insert failed',
101
+ });
99
102
  });
100
103
  });
101
104
 
@@ -177,16 +180,19 @@ describe('resolveBlocker', () => {
177
180
  );
178
181
  });
179
182
 
180
- it('should throw error when API call fails', async () => {
183
+ it('should return error when API call fails', async () => {
181
184
  mockApiClient.resolveBlocker.mockResolvedValue({
182
185
  ok: false,
183
186
  error: 'Update failed',
184
187
  });
185
188
  const ctx = createMockContext();
186
189
 
187
- await expect(
188
- resolveBlocker({ blocker_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
189
- ).rejects.toThrow('Update failed');
190
+ const result = await resolveBlocker({ blocker_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
191
+
192
+ expect(result.isError).toBe(true);
193
+ expect(result.result).toMatchObject({
194
+ error: 'Update failed',
195
+ });
190
196
  });
191
197
  });
192
198
 
@@ -298,16 +304,19 @@ describe('getBlockers', () => {
298
304
  expect(mockApiClient.getBlockers).toHaveBeenCalled();
299
305
  });
300
306
 
301
- it('should throw error when API call fails', async () => {
307
+ it('should return error when API call fails', async () => {
302
308
  mockApiClient.getBlockers.mockResolvedValue({
303
309
  ok: false,
304
310
  error: 'Query failed',
305
311
  });
306
312
  const ctx = createMockContext();
307
313
 
308
- await expect(
309
- getBlockers({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
310
- ).rejects.toThrow('Query failed');
314
+ const result = await getBlockers({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
315
+
316
+ expect(result.isError).toBe(true);
317
+ expect(result.result).toMatchObject({
318
+ error: 'Query failed',
319
+ });
311
320
  });
312
321
  });
313
322
 
@@ -366,15 +375,18 @@ describe('deleteBlocker', () => {
366
375
  );
367
376
  });
368
377
 
369
- it('should throw error when API call fails', async () => {
378
+ it('should return error when API call fails', async () => {
370
379
  mockApiClient.deleteBlocker.mockResolvedValue({
371
380
  ok: false,
372
381
  error: 'Delete failed',
373
382
  });
374
383
  const ctx = createMockContext();
375
384
 
376
- await expect(
377
- deleteBlocker({ blocker_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
378
- ).rejects.toThrow('Delete failed');
385
+ const result = await deleteBlocker({ blocker_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
386
+
387
+ expect(result.isError).toBe(true);
388
+ expect(result.result).toMatchObject({
389
+ error: 'Delete failed',
390
+ });
379
391
  });
380
392
  });
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { Handler, HandlerRegistry } from './types.js';
12
+ import { success, error } from './types.js';
12
13
  import {
13
14
  parseArgs,
14
15
  uuidValidator,
@@ -47,10 +48,10 @@ export const addBlocker: Handler = async (args, ctx) => {
47
48
  const response = await apiClient.addBlocker(project_id, description, ctx.session.currentSessionId || undefined);
48
49
 
49
50
  if (!response.ok) {
50
- throw new Error(response.error || 'Failed to add blocker');
51
+ return error(response.error || 'Failed to add blocker');
51
52
  }
52
53
 
53
- return { result: response.data };
54
+ return success(response.data);
54
55
  };
55
56
 
56
57
  export const resolveBlocker: Handler = async (args, _ctx) => {
@@ -60,10 +61,10 @@ export const resolveBlocker: Handler = async (args, _ctx) => {
60
61
  const response = await apiClient.resolveBlocker(blocker_id, resolution_note);
61
62
 
62
63
  if (!response.ok) {
63
- throw new Error(response.error || 'Failed to resolve blocker');
64
+ return error(response.error || 'Failed to resolve blocker');
64
65
  }
65
66
 
66
- return { result: response.data };
67
+ return success(response.data);
67
68
  };
68
69
 
69
70
  export const getBlockers: Handler = async (args, _ctx) => {
@@ -78,10 +79,10 @@ export const getBlockers: Handler = async (args, _ctx) => {
78
79
  });
79
80
 
80
81
  if (!response.ok) {
81
- throw new Error(response.error || 'Failed to fetch blockers');
82
+ return error(response.error || 'Failed to fetch blockers');
82
83
  }
83
84
 
84
- return { result: response.data };
85
+ return success(response.data);
85
86
  };
86
87
 
87
88
  export const deleteBlocker: Handler = async (args, _ctx) => {
@@ -91,10 +92,10 @@ export const deleteBlocker: Handler = async (args, _ctx) => {
91
92
  const response = await apiClient.deleteBlocker(blocker_id);
92
93
 
93
94
  if (!response.ok) {
94
- throw new Error(response.error || 'Failed to delete blocker');
95
+ return error(response.error || 'Failed to delete blocker');
95
96
  }
96
97
 
97
- return { result: response.data };
98
+ return success(response.data);
98
99
  };
99
100
 
100
101
  /**