@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.
Files changed (93) hide show
  1. package/README.md +63 -38
  2. package/dist/api-client.d.ts +187 -0
  3. package/dist/api-client.js +53 -1
  4. package/dist/handlers/blockers.js +9 -8
  5. package/dist/handlers/bodies-of-work.js +14 -14
  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 +54 -0
  10. package/dist/handlers/decisions.js +3 -3
  11. package/dist/handlers/deployment.js +35 -19
  12. package/dist/handlers/discovery.d.ts +7 -0
  13. package/dist/handlers/discovery.js +61 -2
  14. package/dist/handlers/fallback.js +5 -4
  15. package/dist/handlers/file-checkouts.d.ts +2 -0
  16. package/dist/handlers/file-checkouts.js +38 -6
  17. package/dist/handlers/findings.js +13 -12
  18. package/dist/handlers/git-issues.js +4 -4
  19. package/dist/handlers/ideas.js +5 -5
  20. package/dist/handlers/index.d.ts +1 -0
  21. package/dist/handlers/index.js +3 -0
  22. package/dist/handlers/milestones.js +5 -5
  23. package/dist/handlers/organizations.js +13 -13
  24. package/dist/handlers/progress.js +2 -2
  25. package/dist/handlers/project.js +6 -6
  26. package/dist/handlers/requests.js +3 -3
  27. package/dist/handlers/session.js +28 -9
  28. package/dist/handlers/sprints.js +17 -17
  29. package/dist/handlers/tasks.d.ts +2 -0
  30. package/dist/handlers/tasks.js +78 -20
  31. package/dist/handlers/types.d.ts +64 -2
  32. package/dist/handlers/types.js +48 -1
  33. package/dist/handlers/validation.js +3 -3
  34. package/dist/index.js +7 -2716
  35. package/dist/token-tracking.d.ts +74 -0
  36. package/dist/token-tracking.js +122 -0
  37. package/dist/tools.js +298 -9
  38. package/dist/utils.d.ts +5 -0
  39. package/dist/utils.js +17 -0
  40. package/docs/TOOLS.md +2053 -0
  41. package/package.json +4 -1
  42. package/scripts/generate-docs.ts +212 -0
  43. package/src/api-client.test.ts +723 -0
  44. package/src/api-client.ts +236 -1
  45. package/src/handlers/__test-setup__.ts +9 -0
  46. package/src/handlers/blockers.test.ts +31 -19
  47. package/src/handlers/blockers.ts +9 -8
  48. package/src/handlers/bodies-of-work.test.ts +55 -32
  49. package/src/handlers/bodies-of-work.ts +14 -14
  50. package/src/handlers/connectors.test.ts +834 -0
  51. package/src/handlers/connectors.ts +229 -0
  52. package/src/handlers/cost.ts +66 -0
  53. package/src/handlers/decisions.test.ts +34 -25
  54. package/src/handlers/decisions.ts +3 -3
  55. package/src/handlers/deployment.ts +39 -19
  56. package/src/handlers/discovery.ts +61 -2
  57. package/src/handlers/fallback.test.ts +26 -22
  58. package/src/handlers/fallback.ts +5 -4
  59. package/src/handlers/file-checkouts.test.ts +242 -49
  60. package/src/handlers/file-checkouts.ts +44 -6
  61. package/src/handlers/findings.test.ts +38 -24
  62. package/src/handlers/findings.ts +13 -12
  63. package/src/handlers/git-issues.test.ts +51 -43
  64. package/src/handlers/git-issues.ts +4 -4
  65. package/src/handlers/ideas.test.ts +28 -23
  66. package/src/handlers/ideas.ts +5 -5
  67. package/src/handlers/index.ts +3 -0
  68. package/src/handlers/milestones.test.ts +33 -28
  69. package/src/handlers/milestones.ts +5 -5
  70. package/src/handlers/organizations.test.ts +104 -83
  71. package/src/handlers/organizations.ts +13 -13
  72. package/src/handlers/progress.test.ts +20 -14
  73. package/src/handlers/progress.ts +2 -2
  74. package/src/handlers/project.test.ts +34 -27
  75. package/src/handlers/project.ts +6 -6
  76. package/src/handlers/requests.test.ts +27 -18
  77. package/src/handlers/requests.ts +3 -3
  78. package/src/handlers/session.test.ts +47 -0
  79. package/src/handlers/session.ts +26 -9
  80. package/src/handlers/sprints.test.ts +71 -50
  81. package/src/handlers/sprints.ts +17 -17
  82. package/src/handlers/tasks.test.ts +77 -15
  83. package/src/handlers/tasks.ts +90 -21
  84. package/src/handlers/tool-categories.test.ts +66 -0
  85. package/src/handlers/types.ts +81 -2
  86. package/src/handlers/validation.test.ts +78 -45
  87. package/src/handlers/validation.ts +3 -3
  88. package/src/index.ts +12 -2732
  89. package/src/token-tracking.test.ts +453 -0
  90. package/src/token-tracking.ts +164 -0
  91. package/src/tools.ts +298 -9
  92. package/src/utils.test.ts +2 -2
  93. package/src/utils.ts +17 -0
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Connectors Handlers
3
+ *
4
+ * Handles external integration management:
5
+ * - get_connectors
6
+ * - get_connector
7
+ * - add_connector
8
+ * - update_connector
9
+ * - delete_connector
10
+ * - test_connector
11
+ * - get_connector_events
12
+ */
13
+ import { success, error } from './types.js';
14
+ import { parseArgs, uuidValidator, createEnumValidator, } from '../validators.js';
15
+ import { getApiClient } from '../api-client.js';
16
+ // Valid connector types
17
+ const VALID_CONNECTOR_TYPES = ['webhook', 'slack', 'discord', 'github', 'custom'];
18
+ // Valid connector statuses
19
+ const VALID_CONNECTOR_STATUSES = ['active', 'disabled'];
20
+ // Valid event statuses
21
+ const VALID_EVENT_STATUSES = ['pending', 'sent', 'failed', 'retrying'];
22
+ // Argument schemas for type-safe parsing
23
+ const getConnectorsSchema = {
24
+ project_id: { type: 'string', required: true, validate: uuidValidator },
25
+ type: { type: 'string', validate: createEnumValidator(VALID_CONNECTOR_TYPES) },
26
+ status: { type: 'string', validate: createEnumValidator(VALID_CONNECTOR_STATUSES) },
27
+ limit: { type: 'number', default: 50 },
28
+ offset: { type: 'number', default: 0 },
29
+ };
30
+ const getConnectorSchema = {
31
+ connector_id: { type: 'string', required: true, validate: uuidValidator },
32
+ };
33
+ const addConnectorSchema = {
34
+ project_id: { type: 'string', required: true, validate: uuidValidator },
35
+ name: { type: 'string', required: true },
36
+ type: { type: 'string', required: true, validate: createEnumValidator(VALID_CONNECTOR_TYPES) },
37
+ description: { type: 'string' },
38
+ config: { type: 'object' },
39
+ events: { type: 'object' },
40
+ };
41
+ const updateConnectorSchema = {
42
+ connector_id: { type: 'string', required: true, validate: uuidValidator },
43
+ name: { type: 'string' },
44
+ description: { type: 'string' },
45
+ config: { type: 'object' },
46
+ events: { type: 'object' },
47
+ status: { type: 'string', validate: createEnumValidator(VALID_CONNECTOR_STATUSES) },
48
+ };
49
+ const deleteConnectorSchema = {
50
+ connector_id: { type: 'string', required: true, validate: uuidValidator },
51
+ };
52
+ const testConnectorSchema = {
53
+ connector_id: { type: 'string', required: true, validate: uuidValidator },
54
+ };
55
+ const getConnectorEventsSchema = {
56
+ connector_id: { type: 'string', validate: uuidValidator },
57
+ project_id: { type: 'string', validate: uuidValidator },
58
+ status: { type: 'string', validate: createEnumValidator(VALID_EVENT_STATUSES) },
59
+ limit: { type: 'number', default: 50 },
60
+ offset: { type: 'number', default: 0 },
61
+ };
62
+ /**
63
+ * Get all connectors for a project
64
+ */
65
+ export const getConnectors = async (args, _ctx) => {
66
+ const { project_id, type, status, limit, offset } = parseArgs(args, getConnectorsSchema);
67
+ const apiClient = getApiClient();
68
+ const response = await apiClient.getConnectors(project_id, {
69
+ type,
70
+ status,
71
+ limit,
72
+ offset
73
+ });
74
+ if (!response.ok) {
75
+ return error(response.error || 'Failed to fetch connectors');
76
+ }
77
+ return success(response.data);
78
+ };
79
+ /**
80
+ * Get a single connector with full details
81
+ */
82
+ export const getConnector = async (args, _ctx) => {
83
+ const { connector_id } = parseArgs(args, getConnectorSchema);
84
+ const apiClient = getApiClient();
85
+ const response = await apiClient.getConnector(connector_id);
86
+ if (!response.ok) {
87
+ return error(response.error || 'Failed to fetch connector');
88
+ }
89
+ return success(response.data);
90
+ };
91
+ /**
92
+ * Add a new connector
93
+ */
94
+ export const addConnector = async (args, _ctx) => {
95
+ const { project_id, name, type, description, config, events } = parseArgs(args, addConnectorSchema);
96
+ const apiClient = getApiClient();
97
+ const response = await apiClient.addConnector(project_id, {
98
+ name,
99
+ type,
100
+ description,
101
+ config: config,
102
+ events: events
103
+ });
104
+ if (!response.ok) {
105
+ return error(response.error || 'Failed to create connector');
106
+ }
107
+ return success(response.data);
108
+ };
109
+ /**
110
+ * Update a connector
111
+ */
112
+ export const updateConnector = async (args, _ctx) => {
113
+ const { connector_id, name, description, config, events, status } = parseArgs(args, updateConnectorSchema);
114
+ const apiClient = getApiClient();
115
+ const response = await apiClient.updateConnector(connector_id, {
116
+ name,
117
+ description,
118
+ config: config,
119
+ events: events,
120
+ status
121
+ });
122
+ if (!response.ok) {
123
+ return error(response.error || 'Failed to update connector');
124
+ }
125
+ return success(response.data);
126
+ };
127
+ /**
128
+ * Delete a connector
129
+ */
130
+ export const deleteConnector = async (args, _ctx) => {
131
+ const { connector_id } = parseArgs(args, deleteConnectorSchema);
132
+ const apiClient = getApiClient();
133
+ const response = await apiClient.deleteConnector(connector_id);
134
+ if (!response.ok) {
135
+ return error(response.error || 'Failed to delete connector');
136
+ }
137
+ return success(response.data);
138
+ };
139
+ /**
140
+ * Test a connector by sending a test event
141
+ */
142
+ export const testConnector = async (args, _ctx) => {
143
+ const { connector_id } = parseArgs(args, testConnectorSchema);
144
+ const apiClient = getApiClient();
145
+ const response = await apiClient.testConnector(connector_id);
146
+ if (!response.ok) {
147
+ return error(response.error || 'Failed to test connector');
148
+ }
149
+ return success(response.data);
150
+ };
151
+ /**
152
+ * Get connector event history
153
+ */
154
+ export const getConnectorEvents = async (args, _ctx) => {
155
+ const { connector_id, project_id, status, limit, offset } = parseArgs(args, getConnectorEventsSchema);
156
+ if (!connector_id && !project_id) {
157
+ return error('Either connector_id or project_id is required');
158
+ }
159
+ const apiClient = getApiClient();
160
+ const response = await apiClient.getConnectorEvents({
161
+ connector_id,
162
+ project_id,
163
+ status,
164
+ limit,
165
+ offset
166
+ });
167
+ if (!response.ok) {
168
+ return error(response.error || 'Failed to fetch connector events');
169
+ }
170
+ return success(response.data);
171
+ };
172
+ /**
173
+ * Connectors handlers registry
174
+ */
175
+ export const connectorHandlers = {
176
+ get_connectors: getConnectors,
177
+ get_connector: getConnector,
178
+ add_connector: addConnector,
179
+ update_connector: updateConnector,
180
+ delete_connector: deleteConnector,
181
+ test_connector: testConnector,
182
+ get_connector_events: getConnectorEvents,
183
+ };
@@ -8,6 +8,8 @@
8
8
  * - update_cost_alert
9
9
  * - delete_cost_alert
10
10
  * - get_task_costs
11
+ * - get_body_of_work_costs
12
+ * - get_sprint_costs
11
13
  */
12
14
  import type { Handler, HandlerRegistry } from './types.js';
13
15
  /**
@@ -34,6 +36,14 @@ export declare const deleteCostAlert: Handler;
34
36
  * Get task costs for a project
35
37
  */
36
38
  export declare const getTaskCosts: Handler;
39
+ /**
40
+ * Get body of work costs with phase breakdown
41
+ */
42
+ export declare const getBodyOfWorkCosts: Handler;
43
+ /**
44
+ * Get sprint costs with velocity metrics
45
+ */
46
+ export declare const getSprintCosts: Handler;
37
47
  /**
38
48
  * Cost handlers registry
39
49
  */
@@ -8,6 +8,8 @@
8
8
  * - update_cost_alert
9
9
  * - delete_cost_alert
10
10
  * - get_task_costs
11
+ * - get_body_of_work_costs
12
+ * - get_sprint_costs
11
13
  */
12
14
  import { parseArgs, uuidValidator, createEnumValidator, ValidationError } from '../validators.js';
13
15
  import { getApiClient } from '../api-client.js';
@@ -42,6 +44,14 @@ const getTaskCostsSchema = {
42
44
  project_id: { type: 'string', required: true, validate: uuidValidator },
43
45
  limit: { type: 'number', default: 20 },
44
46
  };
47
+ const getBodyOfWorkCostsSchema = {
48
+ body_of_work_id: { type: 'string', validate: uuidValidator },
49
+ project_id: { type: 'string', validate: uuidValidator },
50
+ };
51
+ const getSprintCostsSchema = {
52
+ sprint_id: { type: 'string', validate: uuidValidator },
53
+ project_id: { type: 'string', validate: uuidValidator },
54
+ };
45
55
  // Custom validator for positive numbers
46
56
  function validatePositiveNumber(value, fieldName) {
47
57
  if (value !== undefined && value <= 0) {
@@ -164,6 +174,48 @@ export const getTaskCosts = async (args, _ctx) => {
164
174
  }
165
175
  return { result: response.data };
166
176
  };
177
+ /**
178
+ * Get body of work costs with phase breakdown
179
+ */
180
+ export const getBodyOfWorkCosts = async (args, _ctx) => {
181
+ const { body_of_work_id, project_id } = parseArgs(args, getBodyOfWorkCostsSchema);
182
+ if (!body_of_work_id && !project_id) {
183
+ return {
184
+ result: { error: 'Either body_of_work_id or project_id is required' },
185
+ isError: true,
186
+ };
187
+ }
188
+ const apiClient = getApiClient();
189
+ const response = await apiClient.getBodyOfWorkCosts({ body_of_work_id, project_id });
190
+ if (!response.ok) {
191
+ return {
192
+ result: { error: response.error || 'Failed to get body of work costs' },
193
+ isError: true,
194
+ };
195
+ }
196
+ return { result: response.data };
197
+ };
198
+ /**
199
+ * Get sprint costs with velocity metrics
200
+ */
201
+ export const getSprintCosts = async (args, _ctx) => {
202
+ const { sprint_id, project_id } = parseArgs(args, getSprintCostsSchema);
203
+ if (!sprint_id && !project_id) {
204
+ return {
205
+ result: { error: 'Either sprint_id or project_id is required' },
206
+ isError: true,
207
+ };
208
+ }
209
+ const apiClient = getApiClient();
210
+ const response = await apiClient.getSprintCosts({ sprint_id, project_id });
211
+ if (!response.ok) {
212
+ return {
213
+ result: { error: response.error || 'Failed to get sprint costs' },
214
+ isError: true,
215
+ };
216
+ }
217
+ return { result: response.data };
218
+ };
167
219
  /**
168
220
  * Cost handlers registry
169
221
  */
@@ -174,4 +226,6 @@ export const costHandlers = {
174
226
  update_cost_alert: updateCostAlert,
175
227
  delete_cost_alert: deleteCostAlert,
176
228
  get_task_costs: getTaskCosts,
229
+ get_body_of_work_costs: getBodyOfWorkCosts,
230
+ get_sprint_costs: getSprintCosts,
177
231
  };
@@ -38,7 +38,7 @@ export const logDecision = async (args, ctx) => {
38
38
  alternatives_considered: alternatives_considered
39
39
  }, session.currentSessionId || undefined);
40
40
  if (!response.ok) {
41
- throw new Error(`Failed to log decision: ${response.error}`);
41
+ return { result: { error: response.error || 'Failed to log decision' }, isError: true };
42
42
  }
43
43
  return { result: { success: true, title, decision_id: response.data?.decision_id } };
44
44
  };
@@ -51,7 +51,7 @@ export const getDecisions = async (args, _ctx) => {
51
51
  search_query
52
52
  });
53
53
  if (!response.ok) {
54
- throw new Error(`Failed to fetch decisions: ${response.error}`);
54
+ return { result: { error: response.error || 'Failed to fetch decisions' }, isError: true };
55
55
  }
56
56
  return {
57
57
  result: {
@@ -64,7 +64,7 @@ export const deleteDecision = async (args, _ctx) => {
64
64
  const apiClient = getApiClient();
65
65
  const response = await apiClient.deleteDecision(decision_id);
66
66
  if (!response.ok) {
67
- throw new Error(`Failed to delete decision: ${response.error}`);
67
+ return { result: { error: response.error || 'Failed to delete decision' }, isError: true };
68
68
  }
69
69
  return { result: { success: true } };
70
70
  };
@@ -20,7 +20,7 @@ const VERSION_BUMPS = ['patch', 'minor', 'major'];
20
20
  const REQUIREMENT_TYPES = ['migration', 'env_var', 'config', 'manual', 'breaking_change', 'agent_task'];
21
21
  const REQUIREMENT_STAGES = ['preparation', 'deployment', 'verification'];
22
22
  const REQUIREMENT_STATUSES = ['pending', 'completed', 'converted_to_task', 'all'];
23
- const SCHEDULE_TYPES = ['once', 'daily', 'weekly', 'monthly'];
23
+ const SCHEDULE_TYPES = ['once', 'hourly', 'daily', 'weekly', 'monthly'];
24
24
  // ============================================================================
25
25
  // Argument Schemas
26
26
  // ============================================================================
@@ -80,6 +80,7 @@ const scheduleDeploymentSchema = {
80
80
  schedule_type: { type: 'string', default: 'once', validate: createEnumValidator(SCHEDULE_TYPES) },
81
81
  scheduled_at: { type: 'string', required: true },
82
82
  auto_trigger: { type: 'boolean', default: true },
83
+ hours_interval: { type: 'number', default: 1 },
83
84
  notes: { type: 'string' },
84
85
  git_ref: { type: 'string' },
85
86
  };
@@ -94,6 +95,7 @@ const updateScheduledDeploymentSchema = {
94
95
  schedule_type: { type: 'string', validate: createEnumValidator(SCHEDULE_TYPES) },
95
96
  scheduled_at: { type: 'string' },
96
97
  auto_trigger: { type: 'boolean' },
98
+ hours_interval: { type: 'number' },
97
99
  enabled: { type: 'boolean' },
98
100
  notes: { type: 'string' },
99
101
  git_ref: { type: 'string' },
@@ -118,7 +120,7 @@ export const requestDeployment = async (args, ctx) => {
118
120
  git_ref
119
121
  });
120
122
  if (!response.ok) {
121
- throw new Error(response.error || 'Failed to request deployment');
123
+ return { result: { error: response.error || 'Failed to request deployment' }, isError: true };
122
124
  }
123
125
  return { result: response.data };
124
126
  };
@@ -128,7 +130,7 @@ export const claimDeploymentValidation = async (args, ctx) => {
128
130
  const apiClient = getApiClient();
129
131
  const response = await apiClient.claimDeploymentValidation(project_id, session.currentSessionId || undefined);
130
132
  if (!response.ok) {
131
- throw new Error(response.error || 'Failed to claim deployment validation');
133
+ return { result: { error: response.error || 'Failed to claim deployment validation' }, isError: true };
132
134
  }
133
135
  return { result: response.data };
134
136
  };
@@ -142,7 +144,7 @@ export const reportValidation = async (args, ctx) => {
142
144
  error_message
143
145
  });
144
146
  if (!response.ok) {
145
- throw new Error(response.error || 'Failed to report validation');
147
+ return { result: { error: response.error || 'Failed to report validation' }, isError: true };
146
148
  }
147
149
  return { result: response.data };
148
150
  };
@@ -151,7 +153,7 @@ export const checkDeploymentStatus = async (args, ctx) => {
151
153
  const apiClient = getApiClient();
152
154
  const response = await apiClient.checkDeploymentStatus(project_id);
153
155
  if (!response.ok) {
154
- throw new Error(response.error || 'Failed to check deployment status');
156
+ return { result: { error: response.error || 'Failed to check deployment status' }, isError: true };
155
157
  }
156
158
  return { result: response.data };
157
159
  };
@@ -161,7 +163,7 @@ export const startDeployment = async (args, ctx) => {
161
163
  const apiClient = getApiClient();
162
164
  const response = await apiClient.startDeployment(project_id, session.currentSessionId || undefined);
163
165
  if (!response.ok) {
164
- throw new Error(response.error || 'Failed to start deployment');
166
+ return { result: { error: response.error || 'Failed to start deployment' }, isError: true };
165
167
  }
166
168
  return { result: response.data };
167
169
  };
@@ -174,7 +176,7 @@ export const completeDeployment = async (args, ctx) => {
174
176
  summary
175
177
  });
176
178
  if (!response.ok) {
177
- throw new Error(response.error || 'Failed to complete deployment');
179
+ return { result: { error: response.error || 'Failed to complete deployment' }, isError: true };
178
180
  }
179
181
  return { result: response.data };
180
182
  };
@@ -183,7 +185,7 @@ export const cancelDeployment = async (args, ctx) => {
183
185
  const apiClient = getApiClient();
184
186
  const response = await apiClient.cancelDeployment(project_id, reason);
185
187
  if (!response.ok) {
186
- throw new Error(response.error || 'Failed to cancel deployment');
188
+ return { result: { error: response.error || 'Failed to cancel deployment' }, isError: true };
187
189
  }
188
190
  return { result: response.data };
189
191
  };
@@ -200,7 +202,7 @@ export const addDeploymentRequirement = async (args, ctx) => {
200
202
  recurring
201
203
  });
202
204
  if (!response.ok) {
203
- throw new Error(response.error || 'Failed to add deployment requirement');
205
+ return { result: { error: response.error || 'Failed to add deployment requirement' }, isError: true };
204
206
  }
205
207
  return { result: response.data };
206
208
  };
@@ -209,7 +211,7 @@ export const completeDeploymentRequirement = async (args, ctx) => {
209
211
  const apiClient = getApiClient();
210
212
  const response = await apiClient.completeDeploymentRequirement(requirement_id);
211
213
  if (!response.ok) {
212
- throw new Error(response.error || 'Failed to complete deployment requirement');
214
+ return { result: { error: response.error || 'Failed to complete deployment requirement' }, isError: true };
213
215
  }
214
216
  return { result: response.data };
215
217
  };
@@ -221,7 +223,7 @@ export const getDeploymentRequirements = async (args, ctx) => {
221
223
  stage: stage
222
224
  });
223
225
  if (!response.ok) {
224
- throw new Error(response.error || 'Failed to get deployment requirements');
226
+ return { result: { error: response.error || 'Failed to get deployment requirements' }, isError: true };
225
227
  }
226
228
  return { result: response.data };
227
229
  };
@@ -229,7 +231,7 @@ export const getDeploymentRequirements = async (args, ctx) => {
229
231
  // Scheduled Deployments
230
232
  // ============================================================================
231
233
  export const scheduleDeployment = async (args, ctx) => {
232
- const { project_id, environment, version_bump, schedule_type, scheduled_at, auto_trigger, notes, git_ref, } = parseArgs(args, scheduleDeploymentSchema);
234
+ const { project_id, environment, version_bump, schedule_type, scheduled_at, auto_trigger, hours_interval, notes, git_ref, } = parseArgs(args, scheduleDeploymentSchema);
233
235
  // Parse and validate scheduled_at
234
236
  const scheduledDate = new Date(scheduled_at);
235
237
  if (isNaN(scheduledDate.getTime())) {
@@ -243,6 +245,13 @@ export const scheduleDeployment = async (args, ctx) => {
243
245
  field: 'scheduled_at',
244
246
  });
245
247
  }
248
+ // Validate hours_interval for hourly schedule type (default is 1)
249
+ const hoursInterval = hours_interval ?? 1;
250
+ if (schedule_type === 'hourly' && (hoursInterval < 1 || hoursInterval > 24)) {
251
+ throw new ValidationError('hours_interval must be between 1 and 24', {
252
+ field: 'hours_interval',
253
+ });
254
+ }
246
255
  const apiClient = getApiClient();
247
256
  const response = await apiClient.scheduleDeployment(project_id, {
248
257
  environment: environment,
@@ -250,11 +259,12 @@ export const scheduleDeployment = async (args, ctx) => {
250
259
  schedule_type: schedule_type,
251
260
  scheduled_at: scheduledDate.toISOString(),
252
261
  auto_trigger,
262
+ hours_interval: hoursInterval,
253
263
  notes,
254
264
  git_ref
255
265
  });
256
266
  if (!response.ok) {
257
- throw new Error(response.error || 'Failed to schedule deployment');
267
+ return { result: { error: response.error || 'Failed to schedule deployment' }, isError: true };
258
268
  }
259
269
  return { result: response.data };
260
270
  };
@@ -263,12 +273,12 @@ export const getScheduledDeployments = async (args, ctx) => {
263
273
  const apiClient = getApiClient();
264
274
  const response = await apiClient.getScheduledDeployments(project_id, include_disabled);
265
275
  if (!response.ok) {
266
- throw new Error(response.error || 'Failed to get scheduled deployments');
276
+ return { result: { error: response.error || 'Failed to get scheduled deployments' }, isError: true };
267
277
  }
268
278
  return { result: response.data };
269
279
  };
270
280
  export const updateScheduledDeployment = async (args, ctx) => {
271
- const { schedule_id, environment, version_bump, schedule_type, scheduled_at, auto_trigger, enabled, notes, git_ref, } = parseArgs(args, updateScheduledDeploymentSchema);
281
+ const { schedule_id, environment, version_bump, schedule_type, scheduled_at, auto_trigger, hours_interval, enabled, notes, git_ref, } = parseArgs(args, updateScheduledDeploymentSchema);
272
282
  const updates = {};
273
283
  if (environment !== undefined)
274
284
  updates.environment = environment;
@@ -285,6 +295,12 @@ export const updateScheduledDeployment = async (args, ctx) => {
285
295
  }
286
296
  if (auto_trigger !== undefined)
287
297
  updates.auto_trigger = auto_trigger;
298
+ if (hours_interval !== undefined) {
299
+ if (hours_interval < 1 || hours_interval > 24) {
300
+ throw new ValidationError('hours_interval must be between 1 and 24');
301
+ }
302
+ updates.hours_interval = hours_interval;
303
+ }
288
304
  if (enabled !== undefined)
289
305
  updates.enabled = enabled;
290
306
  if (notes !== undefined)
@@ -297,7 +313,7 @@ export const updateScheduledDeployment = async (args, ctx) => {
297
313
  const apiClient = getApiClient();
298
314
  const response = await apiClient.updateScheduledDeployment(schedule_id, updates);
299
315
  if (!response.ok) {
300
- throw new Error(response.error || 'Failed to update scheduled deployment');
316
+ return { result: { error: response.error || 'Failed to update scheduled deployment' }, isError: true };
301
317
  }
302
318
  return { result: response.data };
303
319
  };
@@ -306,7 +322,7 @@ export const deleteScheduledDeployment = async (args, ctx) => {
306
322
  const apiClient = getApiClient();
307
323
  const response = await apiClient.deleteScheduledDeployment(schedule_id);
308
324
  if (!response.ok) {
309
- throw new Error(response.error || 'Failed to delete scheduled deployment');
325
+ return { result: { error: response.error || 'Failed to delete scheduled deployment' }, isError: true };
310
326
  }
311
327
  return { result: response.data };
312
328
  };
@@ -316,7 +332,7 @@ export const triggerScheduledDeployment = async (args, ctx) => {
316
332
  const apiClient = getApiClient();
317
333
  const response = await apiClient.triggerScheduledDeployment(schedule_id, session.currentSessionId || undefined);
318
334
  if (!response.ok) {
319
- throw new Error(response.error || 'Failed to trigger scheduled deployment');
335
+ return { result: { error: response.error || 'Failed to trigger scheduled deployment' }, isError: true };
320
336
  }
321
337
  return { result: response.data };
322
338
  };
@@ -325,7 +341,7 @@ export const checkDueDeployments = async (args, ctx) => {
325
341
  const apiClient = getApiClient();
326
342
  const response = await apiClient.checkDueDeployments(project_id);
327
343
  if (!response.ok) {
328
- throw new Error(response.error || 'Failed to check due deployments');
344
+ return { result: { error: response.error || 'Failed to check due deployments' }, isError: true };
329
345
  }
330
346
  return { result: response.data };
331
347
  };
@@ -9,6 +9,13 @@
9
9
  * This saves ~8,000 tokens per schema load.
10
10
  */
11
11
  import type { Handler, HandlerRegistry } from './types.js';
12
+ export declare const TOOL_CATEGORIES: Record<string, {
13
+ description: string;
14
+ tools: Array<{
15
+ name: string;
16
+ brief: string;
17
+ }>;
18
+ }>;
12
19
  export declare const discoverTools: Handler;
13
20
  export declare const getToolInfo: Handler;
14
21
  /**
@@ -30,8 +30,8 @@ async function getToolDocs() {
30
30
  toolInfoCache = TOOL_INFO;
31
31
  return toolInfoCache;
32
32
  }
33
- // Tool categories with brief descriptions
34
- const TOOL_CATEGORIES = {
33
+ // Tool categories with brief descriptions (exported for documentation generation)
34
+ export const TOOL_CATEGORIES = {
35
35
  session: {
36
36
  description: 'Session lifecycle and monitoring',
37
37
  tools: [
@@ -116,6 +116,7 @@ const TOOL_CATEGORIES = {
116
116
  tools: [
117
117
  { name: 'add_finding', brief: 'Record audit finding' },
118
118
  { name: 'get_findings', brief: 'List findings' },
119
+ { name: 'get_findings_stats', brief: 'Get findings statistics' },
119
120
  { name: 'update_finding', brief: 'Update finding status' },
120
121
  { name: 'delete_finding', brief: 'Remove finding' },
121
122
  ],
@@ -169,6 +170,10 @@ const TOOL_CATEGORIES = {
169
170
  { name: 'add_task_to_body_of_work', brief: 'Add task to group' },
170
171
  { name: 'remove_task_from_body_of_work', brief: 'Remove from group' },
171
172
  { name: 'activate_body_of_work', brief: 'Activate for work' },
173
+ { name: 'add_task_dependency', brief: 'Add task dependency' },
174
+ { name: 'remove_task_dependency', brief: 'Remove task dependency' },
175
+ { name: 'get_task_dependencies', brief: 'List task dependencies' },
176
+ { name: 'get_next_body_of_work_task', brief: 'Get next available task' },
172
177
  ],
173
178
  },
174
179
  sprints: {
@@ -222,6 +227,8 @@ const TOOL_CATEGORIES = {
222
227
  { name: 'update_cost_alert', brief: 'Update alert config' },
223
228
  { name: 'delete_cost_alert', brief: 'Remove alert' },
224
229
  { name: 'get_task_costs', brief: 'Cost per task' },
230
+ { name: 'get_body_of_work_costs', brief: 'Cost per body of work' },
231
+ { name: 'get_sprint_costs', brief: 'Cost per sprint' },
225
232
  ],
226
233
  },
227
234
  git_issues: {
@@ -239,6 +246,58 @@ const TOOL_CATEGORIES = {
239
246
  { name: 'query_knowledge_base', brief: 'Aggregated project knowledge in one call' },
240
247
  ],
241
248
  },
249
+ discovery: {
250
+ description: 'Tool discovery and documentation',
251
+ tools: [
252
+ { name: 'discover_tools', brief: 'List tools by category' },
253
+ { name: 'get_tool_info', brief: 'Get detailed tool docs' },
254
+ ],
255
+ },
256
+ subtasks: {
257
+ description: 'Break tasks into smaller pieces',
258
+ tools: [
259
+ { name: 'add_subtask', brief: 'Add subtask to task' },
260
+ { name: 'get_subtasks', brief: 'List task subtasks' },
261
+ ],
262
+ },
263
+ worktrees: {
264
+ description: 'Git worktree management',
265
+ tools: [
266
+ { name: 'get_stale_worktrees', brief: 'Find orphaned worktrees' },
267
+ { name: 'clear_worktree_path', brief: 'Clear worktree from task' },
268
+ ],
269
+ },
270
+ roles: {
271
+ description: 'Agent role management',
272
+ tools: [
273
+ { name: 'get_role_settings', brief: 'Get project role settings' },
274
+ { name: 'update_role_settings', brief: 'Configure role behavior' },
275
+ { name: 'set_session_role', brief: 'Set session role' },
276
+ { name: 'get_agents_by_role', brief: 'List agents by role' },
277
+ ],
278
+ },
279
+ file_locks: {
280
+ description: 'File checkout/locking for multi-agent',
281
+ tools: [
282
+ { name: 'checkout_file', brief: 'Lock file for editing' },
283
+ { name: 'checkin_file', brief: 'Release file lock' },
284
+ { name: 'get_file_checkouts', brief: 'List file locks' },
285
+ { name: 'abandon_checkout', brief: 'Force-release lock' },
286
+ { name: 'is_file_available', brief: 'Check if file is free' },
287
+ ],
288
+ },
289
+ connectors: {
290
+ description: 'External integration connectors',
291
+ tools: [
292
+ { name: 'get_connectors', brief: 'List project connectors' },
293
+ { name: 'get_connector', brief: 'Get connector details' },
294
+ { name: 'add_connector', brief: 'Create new connector' },
295
+ { name: 'update_connector', brief: 'Update connector config' },
296
+ { name: 'delete_connector', brief: 'Remove connector' },
297
+ { name: 'test_connector', brief: 'Send test event' },
298
+ { name: 'get_connector_events', brief: 'Event history' },
299
+ ],
300
+ },
242
301
  };
243
302
  export const discoverTools = async (args) => {
244
303
  const { category } = parseArgs(args, discoverToolsSchema);
@@ -23,6 +23,7 @@ const VALID_ACTIVITIES = [
23
23
  'documentation_review',
24
24
  'dependency_audit',
25
25
  'validate_completed_tasks',
26
+ 'worktree_cleanup',
26
27
  ];
27
28
  // Argument schemas for type-safe parsing
28
29
  const startFallbackActivitySchema = {
@@ -47,7 +48,7 @@ export const startFallbackActivity = async (args, ctx) => {
47
48
  const apiClient = getApiClient();
48
49
  const response = await apiClient.startFallbackActivity(project_id, activity, session.currentSessionId || undefined);
49
50
  if (!response.ok) {
50
- throw new Error(`Failed to start fallback activity: ${response.error}`);
51
+ return { result: { error: response.error || 'Failed to start fallback activity' }, isError: true };
51
52
  }
52
53
  // Get the activity details for the response
53
54
  const activityInfo = FALLBACK_ACTIVITIES.find((a) => a.activity === activity);
@@ -77,7 +78,7 @@ export const stopFallbackActivity = async (args, ctx) => {
77
78
  const apiClient = getApiClient();
78
79
  const response = await apiClient.stopFallbackActivity(project_id, summary, session.currentSessionId || undefined);
79
80
  if (!response.ok) {
80
- throw new Error(`Failed to stop fallback activity: ${response.error}`);
81
+ return { result: { error: response.error || 'Failed to stop fallback activity' }, isError: true };
81
82
  }
82
83
  return {
83
84
  result: {
@@ -96,7 +97,7 @@ export const getActivityHistory = async (args, _ctx) => {
96
97
  limit
97
98
  });
98
99
  if (!response.ok) {
99
- throw new Error(`Failed to get activity history: ${response.error}`);
100
+ return { result: { error: response.error || 'Failed to get activity history' }, isError: true };
100
101
  }
101
102
  return {
102
103
  result: {
@@ -114,7 +115,7 @@ export const getActivitySchedules = async (args, _ctx) => {
114
115
  project_id
115
116
  });
116
117
  if (!response.ok) {
117
- throw new Error(`Failed to get activity schedules: ${response.error}`);
118
+ return { result: { error: response.error || 'Failed to get activity schedules' }, isError: true };
118
119
  }
119
120
  return {
120
121
  result: {