@vibescope/mcp-server 0.2.1 → 0.2.3
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.
- package/README.md +63 -38
- package/dist/api-client.d.ts +187 -0
- package/dist/api-client.js +53 -1
- package/dist/handlers/blockers.js +9 -8
- package/dist/handlers/bodies-of-work.js +14 -14
- package/dist/handlers/connectors.d.ts +45 -0
- package/dist/handlers/connectors.js +183 -0
- package/dist/handlers/cost.d.ts +10 -0
- package/dist/handlers/cost.js +54 -0
- package/dist/handlers/decisions.js +3 -3
- package/dist/handlers/deployment.js +35 -19
- package/dist/handlers/discovery.d.ts +7 -0
- package/dist/handlers/discovery.js +61 -2
- package/dist/handlers/fallback.js +5 -4
- package/dist/handlers/file-checkouts.d.ts +2 -0
- package/dist/handlers/file-checkouts.js +38 -6
- package/dist/handlers/findings.js +13 -12
- package/dist/handlers/git-issues.js +4 -4
- package/dist/handlers/ideas.js +5 -5
- package/dist/handlers/index.d.ts +1 -0
- package/dist/handlers/index.js +3 -0
- package/dist/handlers/milestones.js +5 -5
- package/dist/handlers/organizations.js +13 -13
- package/dist/handlers/progress.js +2 -2
- package/dist/handlers/project.js +6 -6
- package/dist/handlers/requests.js +3 -3
- package/dist/handlers/session.js +28 -9
- package/dist/handlers/sprints.js +17 -17
- package/dist/handlers/tasks.d.ts +2 -0
- package/dist/handlers/tasks.js +78 -20
- package/dist/handlers/types.d.ts +64 -2
- package/dist/handlers/types.js +48 -1
- package/dist/handlers/validation.js +3 -3
- package/dist/index.js +7 -2716
- package/dist/token-tracking.d.ts +74 -0
- package/dist/token-tracking.js +122 -0
- package/dist/tools.js +298 -9
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +17 -0
- package/docs/TOOLS.md +2053 -0
- package/package.json +4 -1
- package/scripts/generate-docs.ts +212 -0
- package/src/api-client.test.ts +723 -0
- package/src/api-client.ts +236 -1
- package/src/handlers/__test-setup__.ts +9 -0
- package/src/handlers/blockers.test.ts +31 -19
- package/src/handlers/blockers.ts +9 -8
- package/src/handlers/bodies-of-work.test.ts +55 -32
- package/src/handlers/bodies-of-work.ts +14 -14
- package/src/handlers/connectors.test.ts +834 -0
- package/src/handlers/connectors.ts +229 -0
- package/src/handlers/cost.ts +66 -0
- package/src/handlers/decisions.test.ts +34 -25
- package/src/handlers/decisions.ts +3 -3
- package/src/handlers/deployment.ts +39 -19
- package/src/handlers/discovery.ts +61 -2
- package/src/handlers/fallback.test.ts +26 -22
- package/src/handlers/fallback.ts +5 -4
- package/src/handlers/file-checkouts.test.ts +242 -49
- package/src/handlers/file-checkouts.ts +44 -6
- package/src/handlers/findings.test.ts +38 -24
- package/src/handlers/findings.ts +13 -12
- package/src/handlers/git-issues.test.ts +51 -43
- package/src/handlers/git-issues.ts +4 -4
- package/src/handlers/ideas.test.ts +28 -23
- package/src/handlers/ideas.ts +5 -5
- package/src/handlers/index.ts +3 -0
- package/src/handlers/milestones.test.ts +33 -28
- package/src/handlers/milestones.ts +5 -5
- package/src/handlers/organizations.test.ts +104 -83
- package/src/handlers/organizations.ts +13 -13
- package/src/handlers/progress.test.ts +20 -14
- package/src/handlers/progress.ts +2 -2
- package/src/handlers/project.test.ts +34 -27
- package/src/handlers/project.ts +6 -6
- package/src/handlers/requests.test.ts +27 -18
- package/src/handlers/requests.ts +3 -3
- package/src/handlers/session.test.ts +47 -0
- package/src/handlers/session.ts +26 -9
- package/src/handlers/sprints.test.ts +71 -50
- package/src/handlers/sprints.ts +17 -17
- package/src/handlers/tasks.test.ts +77 -15
- package/src/handlers/tasks.ts +90 -21
- package/src/handlers/tool-categories.test.ts +66 -0
- package/src/handlers/types.ts +81 -2
- package/src/handlers/validation.test.ts +78 -45
- package/src/handlers/validation.ts +3 -3
- package/src/index.ts +12 -2732
- package/src/token-tracking.test.ts +453 -0
- package/src/token-tracking.ts +164 -0
- package/src/tools.ts +298 -9
- package/src/utils.test.ts +2 -2
- package/src/utils.ts +17 -0
package/src/api-client.ts
CHANGED
|
@@ -123,6 +123,23 @@ export class VibescopeApiClient {
|
|
|
123
123
|
priority: number;
|
|
124
124
|
estimated_minutes?: number;
|
|
125
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
|
+
};
|
|
126
143
|
directive?: string;
|
|
127
144
|
blockers_count?: number;
|
|
128
145
|
validation_count?: number;
|
|
@@ -382,6 +399,7 @@ export class VibescopeApiClient {
|
|
|
382
399
|
progress_note?: string;
|
|
383
400
|
estimated_minutes?: number;
|
|
384
401
|
git_branch?: string;
|
|
402
|
+
worktree_path?: string;
|
|
385
403
|
session_id?: string;
|
|
386
404
|
}): Promise<ApiResponse<{
|
|
387
405
|
success: boolean;
|
|
@@ -426,7 +444,11 @@ export class VibescopeApiClient {
|
|
|
426
444
|
next_action: string;
|
|
427
445
|
warnings?: string[];
|
|
428
446
|
}>> {
|
|
429
|
-
|
|
447
|
+
// Use proxy endpoint for consistency - direct endpoint had routing issues on Vercel
|
|
448
|
+
return this.proxy('complete_task', {
|
|
449
|
+
task_id: taskId,
|
|
450
|
+
summary: params.summary,
|
|
451
|
+
}, params.session_id ? { session_id: params.session_id, persona: null, instance_id: '' } : undefined);
|
|
430
452
|
}
|
|
431
453
|
|
|
432
454
|
async deleteTask(taskId: string): Promise<ApiResponse<{
|
|
@@ -1609,6 +1631,63 @@ export class VibescopeApiClient {
|
|
|
1609
1631
|
});
|
|
1610
1632
|
}
|
|
1611
1633
|
|
|
1634
|
+
async getBodyOfWorkCosts(params: {
|
|
1635
|
+
body_of_work_id?: string;
|
|
1636
|
+
project_id?: string;
|
|
1637
|
+
}): Promise<ApiResponse<{
|
|
1638
|
+
bodies_of_work: Array<{
|
|
1639
|
+
body_of_work_id: string;
|
|
1640
|
+
title: string;
|
|
1641
|
+
project_id: string;
|
|
1642
|
+
status: string;
|
|
1643
|
+
task_count: number;
|
|
1644
|
+
total_cost_usd: number;
|
|
1645
|
+
total_tokens: number;
|
|
1646
|
+
pre_phase_cost_usd: number;
|
|
1647
|
+
core_phase_cost_usd: number;
|
|
1648
|
+
post_phase_cost_usd: number;
|
|
1649
|
+
model_breakdown: Record<string, { input: number; output: number }>;
|
|
1650
|
+
}>;
|
|
1651
|
+
count: number;
|
|
1652
|
+
totals: {
|
|
1653
|
+
total_cost_usd: number;
|
|
1654
|
+
total_tokens: number;
|
|
1655
|
+
total_tasks: number;
|
|
1656
|
+
};
|
|
1657
|
+
}>> {
|
|
1658
|
+
return this.proxy('get_body_of_work_costs', params);
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
async getSprintCosts(params: {
|
|
1662
|
+
sprint_id?: string;
|
|
1663
|
+
project_id?: string;
|
|
1664
|
+
}): Promise<ApiResponse<{
|
|
1665
|
+
sprints: Array<{
|
|
1666
|
+
sprint_id: string;
|
|
1667
|
+
title: string;
|
|
1668
|
+
project_id: string;
|
|
1669
|
+
status: string;
|
|
1670
|
+
sprint_number: number;
|
|
1671
|
+
task_count: number;
|
|
1672
|
+
total_cost_usd: number;
|
|
1673
|
+
total_tokens: number;
|
|
1674
|
+
cost_per_story_point: number | null;
|
|
1675
|
+
committed_points: number;
|
|
1676
|
+
velocity_points: number;
|
|
1677
|
+
model_breakdown: Record<string, { input: number; output: number }>;
|
|
1678
|
+
}>;
|
|
1679
|
+
count: number;
|
|
1680
|
+
totals: {
|
|
1681
|
+
total_cost_usd: number;
|
|
1682
|
+
total_tokens: number;
|
|
1683
|
+
total_tasks: number;
|
|
1684
|
+
total_velocity_points: number;
|
|
1685
|
+
avg_cost_per_point: number | null;
|
|
1686
|
+
};
|
|
1687
|
+
}>> {
|
|
1688
|
+
return this.proxy('get_sprint_costs', params);
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1612
1691
|
async getTokenUsage(): Promise<ApiResponse<{
|
|
1613
1692
|
session_tokens: number;
|
|
1614
1693
|
estimated_cost: number;
|
|
@@ -1695,6 +1774,7 @@ export class VibescopeApiClient {
|
|
|
1695
1774
|
environment?: string;
|
|
1696
1775
|
version_bump?: string;
|
|
1697
1776
|
auto_trigger?: boolean;
|
|
1777
|
+
hours_interval?: number;
|
|
1698
1778
|
notes?: string;
|
|
1699
1779
|
git_ref?: string;
|
|
1700
1780
|
}): Promise<ApiResponse<{
|
|
@@ -1712,6 +1792,7 @@ export class VibescopeApiClient {
|
|
|
1712
1792
|
id: string;
|
|
1713
1793
|
scheduled_at: string;
|
|
1714
1794
|
schedule_type: string;
|
|
1795
|
+
hours_interval: number;
|
|
1715
1796
|
environment: string;
|
|
1716
1797
|
version_bump: string;
|
|
1717
1798
|
auto_trigger: boolean;
|
|
@@ -1731,6 +1812,7 @@ export class VibescopeApiClient {
|
|
|
1731
1812
|
async updateScheduledDeployment(scheduleId: string, updates: {
|
|
1732
1813
|
scheduled_at?: string;
|
|
1733
1814
|
schedule_type?: string;
|
|
1815
|
+
hours_interval?: number;
|
|
1734
1816
|
environment?: string;
|
|
1735
1817
|
version_bump?: string;
|
|
1736
1818
|
auto_trigger?: boolean;
|
|
@@ -1883,6 +1965,159 @@ export class VibescopeApiClient {
|
|
|
1883
1965
|
}>> {
|
|
1884
1966
|
return this.proxy('abandon_checkout', params);
|
|
1885
1967
|
}
|
|
1968
|
+
|
|
1969
|
+
// ============================================================================
|
|
1970
|
+
// Worktree Management
|
|
1971
|
+
// ============================================================================
|
|
1972
|
+
|
|
1973
|
+
async getStaleWorktrees(projectId: string): Promise<ApiResponse<{
|
|
1974
|
+
project_id: string;
|
|
1975
|
+
project_name: string;
|
|
1976
|
+
stale_worktrees: Array<{
|
|
1977
|
+
task_id: string;
|
|
1978
|
+
task_title: string;
|
|
1979
|
+
worktree_path: string;
|
|
1980
|
+
git_branch: string | null;
|
|
1981
|
+
status: string;
|
|
1982
|
+
completed_at: string | null;
|
|
1983
|
+
updated_at: string;
|
|
1984
|
+
pr_url: string | null;
|
|
1985
|
+
stale_reason: 'task_finished' | 'potentially_abandoned';
|
|
1986
|
+
}>;
|
|
1987
|
+
count: number;
|
|
1988
|
+
cleanup_instructions: string[] | null;
|
|
1989
|
+
}>> {
|
|
1990
|
+
return this.request('GET', `/api/mcp/worktrees/stale?project_id=${projectId}`);
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
async clearWorktreePath(taskId: string): Promise<ApiResponse<{
|
|
1994
|
+
success: boolean;
|
|
1995
|
+
task_id: string;
|
|
1996
|
+
}>> {
|
|
1997
|
+
return this.request('PATCH', `/api/mcp/tasks/${taskId}`, { worktree_path: null });
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
// ============================================================================
|
|
2001
|
+
// Connector endpoints
|
|
2002
|
+
// ============================================================================
|
|
2003
|
+
|
|
2004
|
+
async getConnectors(projectId: string, params?: {
|
|
2005
|
+
type?: string;
|
|
2006
|
+
status?: string;
|
|
2007
|
+
limit?: number;
|
|
2008
|
+
offset?: number;
|
|
2009
|
+
}): Promise<ApiResponse<{
|
|
2010
|
+
connectors: Array<{
|
|
2011
|
+
id: string;
|
|
2012
|
+
name: string;
|
|
2013
|
+
type: string;
|
|
2014
|
+
description?: string;
|
|
2015
|
+
status: string;
|
|
2016
|
+
events: Record<string, boolean>;
|
|
2017
|
+
events_sent: number;
|
|
2018
|
+
last_triggered_at?: string;
|
|
2019
|
+
last_error?: string;
|
|
2020
|
+
last_error_at?: string;
|
|
2021
|
+
created_at: string;
|
|
2022
|
+
}>;
|
|
2023
|
+
total_count: number;
|
|
2024
|
+
has_more: boolean;
|
|
2025
|
+
}>> {
|
|
2026
|
+
return this.proxy('get_connectors', {
|
|
2027
|
+
project_id: projectId,
|
|
2028
|
+
...params
|
|
2029
|
+
});
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
async getConnector(connectorId: string): Promise<ApiResponse<{
|
|
2033
|
+
connector: {
|
|
2034
|
+
id: string;
|
|
2035
|
+
name: string;
|
|
2036
|
+
type: string;
|
|
2037
|
+
description?: string;
|
|
2038
|
+
config: Record<string, unknown>;
|
|
2039
|
+
events: Record<string, boolean>;
|
|
2040
|
+
status: string;
|
|
2041
|
+
events_sent: number;
|
|
2042
|
+
last_triggered_at?: string;
|
|
2043
|
+
last_error?: string;
|
|
2044
|
+
last_error_at?: string;
|
|
2045
|
+
created_at: string;
|
|
2046
|
+
};
|
|
2047
|
+
}>> {
|
|
2048
|
+
return this.proxy('get_connector', { connector_id: connectorId });
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
async addConnector(projectId: string, params: {
|
|
2052
|
+
name: string;
|
|
2053
|
+
type: string;
|
|
2054
|
+
description?: string;
|
|
2055
|
+
config?: Record<string, unknown>;
|
|
2056
|
+
events?: Record<string, boolean>;
|
|
2057
|
+
}): Promise<ApiResponse<{
|
|
2058
|
+
success: boolean;
|
|
2059
|
+
connector_id: string;
|
|
2060
|
+
}>> {
|
|
2061
|
+
return this.proxy('add_connector', {
|
|
2062
|
+
project_id: projectId,
|
|
2063
|
+
...params
|
|
2064
|
+
});
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
async updateConnector(connectorId: string, updates: {
|
|
2068
|
+
name?: string;
|
|
2069
|
+
description?: string;
|
|
2070
|
+
config?: Record<string, unknown>;
|
|
2071
|
+
events?: Record<string, boolean>;
|
|
2072
|
+
status?: string;
|
|
2073
|
+
}): Promise<ApiResponse<{
|
|
2074
|
+
success: boolean;
|
|
2075
|
+
connector_id: string;
|
|
2076
|
+
}>> {
|
|
2077
|
+
return this.proxy('update_connector', {
|
|
2078
|
+
connector_id: connectorId,
|
|
2079
|
+
...updates
|
|
2080
|
+
});
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
async deleteConnector(connectorId: string): Promise<ApiResponse<{
|
|
2084
|
+
success: boolean;
|
|
2085
|
+
}>> {
|
|
2086
|
+
return this.proxy('delete_connector', { connector_id: connectorId });
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
async testConnector(connectorId: string): Promise<ApiResponse<{
|
|
2090
|
+
success: boolean;
|
|
2091
|
+
event_id: string;
|
|
2092
|
+
status?: number;
|
|
2093
|
+
error?: string;
|
|
2094
|
+
}>> {
|
|
2095
|
+
return this.proxy('test_connector', { connector_id: connectorId });
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
async getConnectorEvents(params: {
|
|
2099
|
+
connector_id?: string;
|
|
2100
|
+
project_id?: string;
|
|
2101
|
+
status?: string;
|
|
2102
|
+
limit?: number;
|
|
2103
|
+
offset?: number;
|
|
2104
|
+
}): Promise<ApiResponse<{
|
|
2105
|
+
events: Array<{
|
|
2106
|
+
id: string;
|
|
2107
|
+
connector_id: string;
|
|
2108
|
+
event_type: string;
|
|
2109
|
+
status: string;
|
|
2110
|
+
response_status?: number;
|
|
2111
|
+
error_message?: string;
|
|
2112
|
+
attempts: number;
|
|
2113
|
+
created_at: string;
|
|
2114
|
+
sent_at?: string;
|
|
2115
|
+
}>;
|
|
2116
|
+
total_count: number;
|
|
2117
|
+
has_more: boolean;
|
|
2118
|
+
}>> {
|
|
2119
|
+
return this.proxy('get_connector_events', params);
|
|
2120
|
+
}
|
|
1886
2121
|
}
|
|
1887
2122
|
|
|
1888
2123
|
// Singleton instance
|
|
@@ -176,6 +176,15 @@ export const mockApiClient = {
|
|
|
176
176
|
getFileCheckouts: vi.fn(),
|
|
177
177
|
abandonCheckout: vi.fn(),
|
|
178
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
|
+
|
|
179
188
|
// Proxy (generic)
|
|
180
189
|
proxy: vi.fn(),
|
|
181
190
|
};
|
|
@@ -83,19 +83,22 @@ describe('addBlocker', () => {
|
|
|
83
83
|
);
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
it('should
|
|
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
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
).
|
|
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
|
|
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
|
|
188
|
-
|
|
189
|
-
).
|
|
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
|
|
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
|
|
309
|
-
|
|
310
|
-
).
|
|
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
|
|
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
|
|
377
|
-
|
|
378
|
-
).
|
|
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
|
});
|
package/src/handlers/blockers.ts
CHANGED
|
@@ -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
|
-
|
|
51
|
+
return error(response.error || 'Failed to add blocker');
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
return
|
|
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
|
-
|
|
64
|
+
return error(response.error || 'Failed to resolve blocker');
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
return
|
|
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
|
-
|
|
82
|
+
return error(response.error || 'Failed to fetch blockers');
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
return
|
|
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
|
-
|
|
95
|
+
return error(response.error || 'Failed to delete blocker');
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
return
|
|
98
|
+
return success(response.data);
|
|
98
99
|
};
|
|
99
100
|
|
|
100
101
|
/**
|
|
@@ -109,16 +109,19 @@ describe('createBodyOfWork', () => {
|
|
|
109
109
|
);
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
-
it('should
|
|
112
|
+
it('should return error when API call fails', async () => {
|
|
113
113
|
const ctx = createMockContext();
|
|
114
114
|
mockApiClient.proxy.mockResolvedValue({
|
|
115
115
|
ok: false,
|
|
116
116
|
error: 'Insert failed',
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
-
await
|
|
120
|
-
|
|
121
|
-
).
|
|
119
|
+
const result = await createBodyOfWork({ project_id: VALID_UUID, title: 'Test' }, ctx);
|
|
120
|
+
|
|
121
|
+
expect(result.isError).toBe(true);
|
|
122
|
+
expect(result.result).toMatchObject({
|
|
123
|
+
error: 'Insert failed',
|
|
124
|
+
});
|
|
122
125
|
});
|
|
123
126
|
});
|
|
124
127
|
|
|
@@ -201,16 +204,19 @@ describe('getBodyOfWork', () => {
|
|
|
201
204
|
await expect(getBodyOfWork({}, ctx)).rejects.toThrow(ValidationError);
|
|
202
205
|
});
|
|
203
206
|
|
|
204
|
-
it('should
|
|
207
|
+
it('should return error when body of work not found', async () => {
|
|
205
208
|
const ctx = createMockContext();
|
|
206
209
|
mockApiClient.proxy.mockResolvedValue({
|
|
207
210
|
ok: false,
|
|
208
211
|
error: 'Not found',
|
|
209
212
|
});
|
|
210
213
|
|
|
211
|
-
await
|
|
212
|
-
|
|
213
|
-
).
|
|
214
|
+
const result = await getBodyOfWork({ body_of_work_id: VALID_UUID }, ctx);
|
|
215
|
+
|
|
216
|
+
expect(result.isError).toBe(true);
|
|
217
|
+
expect(result.result).toMatchObject({
|
|
218
|
+
error: 'Not found',
|
|
219
|
+
});
|
|
214
220
|
});
|
|
215
221
|
|
|
216
222
|
it('should return body of work with tasks organized by phase', async () => {
|
|
@@ -331,16 +337,19 @@ describe('addTaskToBodyOfWork', () => {
|
|
|
331
337
|
).rejects.toThrow(ValidationError);
|
|
332
338
|
});
|
|
333
339
|
|
|
334
|
-
it('should
|
|
340
|
+
it('should return error when API returns error', async () => {
|
|
335
341
|
const ctx = createMockContext();
|
|
336
342
|
mockApiClient.proxy.mockResolvedValue({
|
|
337
343
|
ok: false,
|
|
338
344
|
error: 'Body of work not found',
|
|
339
345
|
});
|
|
340
346
|
|
|
341
|
-
await
|
|
342
|
-
|
|
343
|
-
).
|
|
347
|
+
const result = await addTaskToBodyOfWork({ body_of_work_id: VALID_UUID, task_id: VALID_UUID_2 }, ctx);
|
|
348
|
+
|
|
349
|
+
expect(result.isError).toBe(true);
|
|
350
|
+
expect(result.result).toMatchObject({
|
|
351
|
+
error: 'Body of work not found',
|
|
352
|
+
});
|
|
344
353
|
});
|
|
345
354
|
|
|
346
355
|
it('should add task with default phase "core"', async () => {
|
|
@@ -435,16 +444,19 @@ describe('activateBodyOfWork', () => {
|
|
|
435
444
|
await expect(activateBodyOfWork({}, ctx)).rejects.toThrow(ValidationError);
|
|
436
445
|
});
|
|
437
446
|
|
|
438
|
-
it('should
|
|
447
|
+
it('should return error when API returns error', async () => {
|
|
439
448
|
const ctx = createMockContext();
|
|
440
449
|
mockApiClient.proxy.mockResolvedValue({
|
|
441
450
|
ok: false,
|
|
442
451
|
error: 'Body of work not found',
|
|
443
452
|
});
|
|
444
453
|
|
|
445
|
-
await
|
|
446
|
-
|
|
447
|
-
).
|
|
454
|
+
const result = await activateBodyOfWork({ body_of_work_id: VALID_UUID }, ctx);
|
|
455
|
+
|
|
456
|
+
expect(result.isError).toBe(true);
|
|
457
|
+
expect(result.result).toMatchObject({
|
|
458
|
+
error: 'Body of work not found',
|
|
459
|
+
});
|
|
448
460
|
});
|
|
449
461
|
|
|
450
462
|
it('should activate body of work successfully', async () => {
|
|
@@ -484,15 +496,19 @@ describe('addTaskDependency', () => {
|
|
|
484
496
|
).rejects.toThrow(ValidationError);
|
|
485
497
|
});
|
|
486
498
|
|
|
487
|
-
it('should
|
|
499
|
+
it('should return error when task depends on itself', async () => {
|
|
488
500
|
const ctx = createMockContext();
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
501
|
+
|
|
502
|
+
const result = await addTaskDependency({
|
|
503
|
+
body_of_work_id: VALID_UUID,
|
|
504
|
+
task_id: VALID_UUID_2,
|
|
505
|
+
depends_on_task_id: VALID_UUID_2,
|
|
506
|
+
}, ctx);
|
|
507
|
+
|
|
508
|
+
expect(result.isError).toBe(true);
|
|
509
|
+
expect(result.result).toMatchObject({
|
|
510
|
+
error: 'A task cannot depend on itself',
|
|
511
|
+
});
|
|
496
512
|
});
|
|
497
513
|
|
|
498
514
|
it('should add dependency successfully', async () => {
|
|
@@ -572,11 +588,15 @@ describe('removeTaskDependency', () => {
|
|
|
572
588
|
describe('getTaskDependencies', () => {
|
|
573
589
|
beforeEach(() => vi.clearAllMocks());
|
|
574
590
|
|
|
575
|
-
it('should
|
|
591
|
+
it('should return error when neither body_of_work_id nor task_id provided', async () => {
|
|
576
592
|
const ctx = createMockContext();
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
593
|
+
|
|
594
|
+
const result = await getTaskDependencies({}, ctx);
|
|
595
|
+
|
|
596
|
+
expect(result.isError).toBe(true);
|
|
597
|
+
expect(result.result).toMatchObject({
|
|
598
|
+
error: 'Either body_of_work_id or task_id is required',
|
|
599
|
+
});
|
|
580
600
|
});
|
|
581
601
|
|
|
582
602
|
it('should return dependencies filtered by body_of_work_id', async () => {
|
|
@@ -626,16 +646,19 @@ describe('getNextBodyOfWorkTask', () => {
|
|
|
626
646
|
await expect(getNextBodyOfWorkTask({}, ctx)).rejects.toThrow(ValidationError);
|
|
627
647
|
});
|
|
628
648
|
|
|
629
|
-
it('should
|
|
649
|
+
it('should return error when API returns error', async () => {
|
|
630
650
|
const ctx = createMockContext();
|
|
631
651
|
mockApiClient.proxy.mockResolvedValue({
|
|
632
652
|
ok: false,
|
|
633
653
|
error: 'Body of work not found',
|
|
634
654
|
});
|
|
635
655
|
|
|
636
|
-
await
|
|
637
|
-
|
|
638
|
-
).
|
|
656
|
+
const result = await getNextBodyOfWorkTask({ body_of_work_id: VALID_UUID }, ctx);
|
|
657
|
+
|
|
658
|
+
expect(result.isError).toBe(true);
|
|
659
|
+
expect(result.result).toMatchObject({
|
|
660
|
+
error: 'Body of work not found',
|
|
661
|
+
});
|
|
639
662
|
});
|
|
640
663
|
|
|
641
664
|
it('should return null when no tasks available', async () => {
|