@vibescope/mcp-server 0.2.0 → 0.2.1

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 (65) hide show
  1. package/dist/api-client.d.ts +64 -1
  2. package/dist/api-client.js +34 -3
  3. package/dist/handlers/bodies-of-work.js +82 -49
  4. package/dist/handlers/cost.js +62 -54
  5. package/dist/handlers/decisions.js +29 -16
  6. package/dist/handlers/deployment.js +112 -106
  7. package/dist/handlers/discovery.js +35 -5
  8. package/dist/handlers/fallback.js +24 -19
  9. package/dist/handlers/file-checkouts.d.ts +18 -0
  10. package/dist/handlers/file-checkouts.js +101 -0
  11. package/dist/handlers/findings.d.ts +6 -0
  12. package/dist/handlers/findings.js +85 -30
  13. package/dist/handlers/git-issues.js +36 -32
  14. package/dist/handlers/ideas.js +44 -26
  15. package/dist/handlers/index.d.ts +2 -0
  16. package/dist/handlers/index.js +6 -0
  17. package/dist/handlers/milestones.js +34 -27
  18. package/dist/handlers/organizations.js +86 -78
  19. package/dist/handlers/progress.js +22 -11
  20. package/dist/handlers/project.js +62 -22
  21. package/dist/handlers/requests.js +15 -11
  22. package/dist/handlers/roles.d.ts +18 -0
  23. package/dist/handlers/roles.js +130 -0
  24. package/dist/handlers/session.js +30 -8
  25. package/dist/handlers/sprints.js +76 -64
  26. package/dist/handlers/tasks.js +113 -73
  27. package/dist/handlers/validation.js +18 -14
  28. package/dist/tools.js +387 -0
  29. package/package.json +1 -1
  30. package/src/api-client.ts +89 -6
  31. package/src/handlers/__test-setup__.ts +7 -0
  32. package/src/handlers/bodies-of-work.ts +101 -101
  33. package/src/handlers/cost.test.ts +34 -44
  34. package/src/handlers/cost.ts +77 -92
  35. package/src/handlers/decisions.test.ts +3 -2
  36. package/src/handlers/decisions.ts +32 -27
  37. package/src/handlers/deployment.ts +142 -190
  38. package/src/handlers/discovery.test.ts +4 -5
  39. package/src/handlers/discovery.ts +37 -6
  40. package/src/handlers/fallback.ts +31 -29
  41. package/src/handlers/file-checkouts.test.ts +477 -0
  42. package/src/handlers/file-checkouts.ts +127 -0
  43. package/src/handlers/findings.test.ts +145 -0
  44. package/src/handlers/findings.ts +101 -64
  45. package/src/handlers/git-issues.ts +40 -80
  46. package/src/handlers/ideas.ts +56 -54
  47. package/src/handlers/index.ts +6 -0
  48. package/src/handlers/milestones.test.ts +1 -1
  49. package/src/handlers/milestones.ts +47 -45
  50. package/src/handlers/organizations.ts +104 -129
  51. package/src/handlers/progress.ts +24 -22
  52. package/src/handlers/project.ts +89 -57
  53. package/src/handlers/requests.ts +18 -14
  54. package/src/handlers/roles.test.ts +303 -0
  55. package/src/handlers/roles.ts +208 -0
  56. package/src/handlers/session.ts +39 -17
  57. package/src/handlers/sprints.ts +96 -129
  58. package/src/handlers/tasks.ts +144 -138
  59. package/src/handlers/validation.test.ts +1 -1
  60. package/src/handlers/validation.ts +20 -22
  61. package/src/tools.ts +387 -0
  62. package/dist/config/tool-categories.d.ts +0 -31
  63. package/dist/config/tool-categories.js +0 -253
  64. package/dist/knowledge.d.ts +0 -6
  65. package/dist/knowledge.js +0 -218
@@ -209,6 +209,7 @@ export declare class VibescopeApiClient {
209
209
  estimated_minutes?: number;
210
210
  blocking?: boolean;
211
211
  session_id?: string;
212
+ task_type?: string;
212
213
  }): Promise<ApiResponse<{
213
214
  success: boolean;
214
215
  task_id: string;
@@ -375,7 +376,11 @@ export declare class VibescopeApiClient {
375
376
  deleteBlocker(blockerId: string): Promise<ApiResponse<{
376
377
  success: boolean;
377
378
  }>>;
378
- getDecisions(projectId: string): Promise<ApiResponse<{
379
+ getDecisions(projectId: string, options?: {
380
+ limit?: number;
381
+ offset?: number;
382
+ search_query?: string;
383
+ }): Promise<ApiResponse<{
379
384
  decisions: Array<{
380
385
  id: string;
381
386
  title: string;
@@ -662,6 +667,8 @@ export declare class VibescopeApiClient {
662
667
  getActivityFeed(projectId: string, params?: {
663
668
  limit?: number;
664
669
  since?: string;
670
+ types?: string[];
671
+ created_by?: string;
665
672
  }): Promise<ApiResponse<{
666
673
  activities: Array<{
667
674
  type: string;
@@ -730,6 +737,7 @@ export declare class VibescopeApiClient {
730
737
  success: boolean;
731
738
  }>>;
732
739
  queryKnowledgeBase(projectId: string, params?: {
740
+ scope?: 'summary' | 'detailed';
733
741
  categories?: string[];
734
742
  limit?: number;
735
743
  search_query?: string;
@@ -739,22 +747,35 @@ export declare class VibescopeApiClient {
739
747
  title: string;
740
748
  category: string;
741
749
  severity: string;
750
+ description?: string;
742
751
  }>;
743
752
  decisions?: Array<{
744
753
  id: string;
745
754
  title: string;
746
755
  description: string;
756
+ rationale?: string;
747
757
  }>;
748
758
  completed_tasks?: Array<{
749
759
  id: string;
750
760
  title: string;
751
761
  completed_at: string;
762
+ summary?: string;
752
763
  }>;
753
764
  resolved_blockers?: Array<{
754
765
  id: string;
755
766
  description: string;
756
767
  resolution_note?: string;
757
768
  }>;
769
+ progress?: Array<{
770
+ id: string;
771
+ summary: string;
772
+ details?: string;
773
+ }>;
774
+ qa?: Array<{
775
+ id: string;
776
+ question: string;
777
+ answer: string;
778
+ }>;
758
779
  }>>;
759
780
  syncSession(sessionId: string, params?: {
760
781
  total_tokens?: number;
@@ -1163,6 +1184,48 @@ export declare class VibescopeApiClient {
1163
1184
  slug: string;
1164
1185
  title: string;
1165
1186
  }>>>;
1187
+ checkoutFile(projectId: string, filePath: string, reason?: string, sessionId?: string): Promise<ApiResponse<{
1188
+ success: boolean;
1189
+ checkout_id: string;
1190
+ file_path: string;
1191
+ already_checked_out?: boolean;
1192
+ message: string;
1193
+ }>>;
1194
+ checkinFile(params: {
1195
+ checkout_id?: string;
1196
+ project_id?: string;
1197
+ file_path?: string;
1198
+ summary?: string;
1199
+ }, sessionId?: string): Promise<ApiResponse<{
1200
+ success: boolean;
1201
+ checkout_id: string;
1202
+ message: string;
1203
+ }>>;
1204
+ getFileCheckouts(projectId: string, options?: {
1205
+ status?: string;
1206
+ file_path?: string;
1207
+ limit?: number;
1208
+ }): Promise<ApiResponse<{
1209
+ checkouts: Array<{
1210
+ id: string;
1211
+ file_path: string;
1212
+ status: string;
1213
+ checked_out_at: string;
1214
+ checkout_reason?: string;
1215
+ checked_in_at?: string;
1216
+ checkin_summary?: string;
1217
+ checked_out_by?: string;
1218
+ }>;
1219
+ }>>;
1220
+ abandonCheckout(params: {
1221
+ checkout_id?: string;
1222
+ project_id?: string;
1223
+ file_path?: string;
1224
+ }): Promise<ApiResponse<{
1225
+ success: boolean;
1226
+ checkout_id: string;
1227
+ message: string;
1228
+ }>>;
1166
1229
  }
1167
1230
  export declare function getApiClient(): VibescopeApiClient;
1168
1231
  export declare function initApiClient(config: ApiClientConfig): VibescopeApiClient;
@@ -28,7 +28,8 @@ export class VibescopeApiClient {
28
28
  return {
29
29
  ok: false,
30
30
  status: response.status,
31
- error: data.error || `HTTP ${response.status}`
31
+ error: data.error || `HTTP ${response.status}`,
32
+ data // Include full response data for additional error context
32
33
  };
33
34
  }
34
35
  return {
@@ -171,8 +172,8 @@ export class VibescopeApiClient {
171
172
  // ============================================================================
172
173
  // Decision endpoints
173
174
  // ============================================================================
174
- async getDecisions(projectId) {
175
- return this.proxy('get_decisions', { project_id: projectId });
175
+ async getDecisions(projectId, options) {
176
+ return this.proxy('get_decisions', { project_id: projectId, ...options });
176
177
  }
177
178
  async logDecision(projectId, params, sessionId) {
178
179
  return this.proxy('log_decision', {
@@ -694,6 +695,36 @@ export class VibescopeApiClient {
694
695
  async getHelpTopics() {
695
696
  return this.proxy('get_help_topics', {});
696
697
  }
698
+ // ============================================================================
699
+ // File Checkout endpoints (multi-agent coordination)
700
+ // ============================================================================
701
+ async checkoutFile(projectId, filePath, reason, sessionId) {
702
+ return this.proxy('checkout_file', {
703
+ project_id: projectId,
704
+ file_path: filePath,
705
+ reason
706
+ }, sessionId ? {
707
+ session_id: sessionId,
708
+ persona: null,
709
+ instance_id: ''
710
+ } : undefined);
711
+ }
712
+ async checkinFile(params, sessionId) {
713
+ return this.proxy('checkin_file', params, sessionId ? {
714
+ session_id: sessionId,
715
+ persona: null,
716
+ instance_id: ''
717
+ } : undefined);
718
+ }
719
+ async getFileCheckouts(projectId, options) {
720
+ return this.proxy('get_file_checkouts', {
721
+ project_id: projectId,
722
+ ...options
723
+ });
724
+ }
725
+ async abandonCheckout(params) {
726
+ return this.proxy('abandon_checkout', params);
727
+ }
697
728
  }
698
729
  // Singleton instance
699
730
  let apiClient = null;
@@ -17,13 +17,78 @@
17
17
  *
18
18
  * MIGRATED: Uses Vibescope API client instead of direct Supabase
19
19
  */
20
- import { validateRequired, validateUUID } from '../validators.js';
20
+ import { parseArgs, uuidValidator, createEnumValidator } from '../validators.js';
21
21
  import { getApiClient } from '../api-client.js';
22
+ const BODY_OF_WORK_STATUSES = ['draft', 'active', 'completed', 'cancelled'];
23
+ const TASK_PHASES = ['pre', 'core', 'post'];
24
+ const DEPLOY_ENVIRONMENTS = ['development', 'staging', 'production'];
25
+ const VERSION_BUMPS = ['patch', 'minor', 'major'];
26
+ const DEPLOY_TRIGGERS = ['all_completed', 'all_completed_validated'];
27
+ // ============================================================================
28
+ // Argument Schemas
29
+ // ============================================================================
30
+ const createBodyOfWorkSchema = {
31
+ project_id: { type: 'string', required: true, validate: uuidValidator },
32
+ title: { type: 'string', required: true },
33
+ description: { type: 'string' },
34
+ auto_deploy_on_completion: { type: 'boolean' },
35
+ deploy_environment: { type: 'string', validate: createEnumValidator(DEPLOY_ENVIRONMENTS) },
36
+ deploy_version_bump: { type: 'string', validate: createEnumValidator(VERSION_BUMPS) },
37
+ deploy_trigger: { type: 'string', validate: createEnumValidator(DEPLOY_TRIGGERS) },
38
+ };
39
+ const updateBodyOfWorkSchema = {
40
+ body_of_work_id: { type: 'string', required: true, validate: uuidValidator },
41
+ title: { type: 'string' },
42
+ description: { type: 'string' },
43
+ auto_deploy_on_completion: { type: 'boolean' },
44
+ deploy_environment: { type: 'string', validate: createEnumValidator(DEPLOY_ENVIRONMENTS) },
45
+ deploy_version_bump: { type: 'string', validate: createEnumValidator(VERSION_BUMPS) },
46
+ deploy_trigger: { type: 'string', validate: createEnumValidator(DEPLOY_TRIGGERS) },
47
+ };
48
+ const getBodyOfWorkSchema = {
49
+ body_of_work_id: { type: 'string', required: true, validate: uuidValidator },
50
+ summary_only: { type: 'boolean', default: false },
51
+ };
52
+ const getBodiesOfWorkSchema = {
53
+ project_id: { type: 'string', required: true, validate: uuidValidator },
54
+ status: { type: 'string', validate: createEnumValidator(BODY_OF_WORK_STATUSES) },
55
+ limit: { type: 'number', default: 50 },
56
+ offset: { type: 'number', default: 0 },
57
+ search_query: { type: 'string' },
58
+ };
59
+ const deleteBodyOfWorkSchema = {
60
+ body_of_work_id: { type: 'string', required: true, validate: uuidValidator },
61
+ };
62
+ const addTaskToBodyOfWorkSchema = {
63
+ body_of_work_id: { type: 'string', required: true, validate: uuidValidator },
64
+ task_id: { type: 'string', required: true, validate: uuidValidator },
65
+ phase: { type: 'string', validate: createEnumValidator(TASK_PHASES) },
66
+ order_index: { type: 'number' },
67
+ };
68
+ const removeTaskFromBodyOfWorkSchema = {
69
+ task_id: { type: 'string', required: true, validate: uuidValidator },
70
+ };
71
+ const activateBodyOfWorkSchema = {
72
+ body_of_work_id: { type: 'string', required: true, validate: uuidValidator },
73
+ };
74
+ const addTaskDependencySchema = {
75
+ body_of_work_id: { type: 'string', required: true, validate: uuidValidator },
76
+ task_id: { type: 'string', required: true, validate: uuidValidator },
77
+ depends_on_task_id: { type: 'string', required: true, validate: uuidValidator },
78
+ };
79
+ const removeTaskDependencySchema = {
80
+ task_id: { type: 'string', required: true, validate: uuidValidator },
81
+ depends_on_task_id: { type: 'string', required: true, validate: uuidValidator },
82
+ };
83
+ const getTaskDependenciesSchema = {
84
+ body_of_work_id: { type: 'string', validate: uuidValidator },
85
+ task_id: { type: 'string', validate: uuidValidator },
86
+ };
87
+ const getNextBodyOfWorkTaskSchema = {
88
+ body_of_work_id: { type: 'string', required: true, validate: uuidValidator },
89
+ };
22
90
  export const createBodyOfWork = async (args, ctx) => {
23
- const { project_id, title, description, auto_deploy_on_completion, deploy_environment, deploy_version_bump, deploy_trigger, } = args;
24
- validateRequired(project_id, 'project_id');
25
- validateUUID(project_id, 'project_id');
26
- validateRequired(title, 'title');
91
+ const { project_id, title, description, auto_deploy_on_completion, deploy_environment, deploy_version_bump, deploy_trigger, } = parseArgs(args, createBodyOfWorkSchema);
27
92
  const { session } = ctx;
28
93
  const apiClient = getApiClient();
29
94
  const response = await apiClient.proxy('create_body_of_work', {
@@ -53,9 +118,7 @@ export const createBodyOfWork = async (args, ctx) => {
53
118
  };
54
119
  };
55
120
  export const updateBodyOfWork = async (args, ctx) => {
56
- const { body_of_work_id, title, description, auto_deploy_on_completion, deploy_environment, deploy_version_bump, deploy_trigger, } = args;
57
- validateRequired(body_of_work_id, 'body_of_work_id');
58
- validateUUID(body_of_work_id, 'body_of_work_id');
121
+ const { body_of_work_id, title, description, auto_deploy_on_completion, deploy_environment, deploy_version_bump, deploy_trigger, } = parseArgs(args, updateBodyOfWorkSchema);
59
122
  // Check if any updates provided
60
123
  if (title === undefined && description === undefined && auto_deploy_on_completion === undefined &&
61
124
  deploy_environment === undefined && deploy_version_bump === undefined && deploy_trigger === undefined) {
@@ -77,9 +140,7 @@ export const updateBodyOfWork = async (args, ctx) => {
77
140
  return { result: { success: true, body_of_work_id } };
78
141
  };
79
142
  export const getBodyOfWork = async (args, ctx) => {
80
- const { body_of_work_id, summary_only = false } = args;
81
- validateRequired(body_of_work_id, 'body_of_work_id');
82
- validateUUID(body_of_work_id, 'body_of_work_id');
143
+ const { body_of_work_id, summary_only } = parseArgs(args, getBodyOfWorkSchema);
83
144
  const apiClient = getApiClient();
84
145
  // Response type varies based on summary_only
85
146
  const response = await apiClient.proxy('get_body_of_work', { body_of_work_id, summary_only });
@@ -89,14 +150,12 @@ export const getBodyOfWork = async (args, ctx) => {
89
150
  return { result: response.data };
90
151
  };
91
152
  export const getBodiesOfWork = async (args, ctx) => {
92
- const { project_id, status, limit = 50, offset = 0, search_query } = args;
93
- validateRequired(project_id, 'project_id');
94
- validateUUID(project_id, 'project_id');
153
+ const { project_id, status, limit, offset, search_query } = parseArgs(args, getBodiesOfWorkSchema);
95
154
  const apiClient = getApiClient();
96
155
  const response = await apiClient.proxy('get_bodies_of_work', {
97
156
  project_id,
98
157
  status,
99
- limit: Math.min(limit, 100),
158
+ limit: Math.min(limit ?? 50, 100),
100
159
  offset,
101
160
  search_query
102
161
  });
@@ -106,9 +165,7 @@ export const getBodiesOfWork = async (args, ctx) => {
106
165
  return { result: response.data };
107
166
  };
108
167
  export const deleteBodyOfWork = async (args, ctx) => {
109
- const { body_of_work_id } = args;
110
- validateRequired(body_of_work_id, 'body_of_work_id');
111
- validateUUID(body_of_work_id, 'body_of_work_id');
168
+ const { body_of_work_id } = parseArgs(args, deleteBodyOfWorkSchema);
112
169
  const apiClient = getApiClient();
113
170
  const response = await apiClient.proxy('delete_body_of_work', {
114
171
  body_of_work_id
@@ -119,11 +176,7 @@ export const deleteBodyOfWork = async (args, ctx) => {
119
176
  return { result: { success: true, message: 'Body of work deleted. Tasks are preserved.' } };
120
177
  };
121
178
  export const addTaskToBodyOfWork = async (args, ctx) => {
122
- const { body_of_work_id, task_id, phase, order_index } = args;
123
- validateRequired(body_of_work_id, 'body_of_work_id');
124
- validateUUID(body_of_work_id, 'body_of_work_id');
125
- validateRequired(task_id, 'task_id');
126
- validateUUID(task_id, 'task_id');
179
+ const { body_of_work_id, task_id, phase, order_index } = parseArgs(args, addTaskToBodyOfWorkSchema);
127
180
  const apiClient = getApiClient();
128
181
  const response = await apiClient.proxy('add_task_to_body_of_work', {
129
182
  body_of_work_id,
@@ -137,9 +190,7 @@ export const addTaskToBodyOfWork = async (args, ctx) => {
137
190
  return { result: response.data };
138
191
  };
139
192
  export const removeTaskFromBodyOfWork = async (args, ctx) => {
140
- const { task_id } = args;
141
- validateRequired(task_id, 'task_id');
142
- validateUUID(task_id, 'task_id');
193
+ const { task_id } = parseArgs(args, removeTaskFromBodyOfWorkSchema);
143
194
  const apiClient = getApiClient();
144
195
  const response = await apiClient.proxy('remove_task_from_body_of_work', { task_id });
145
196
  if (!response.ok) {
@@ -148,9 +199,7 @@ export const removeTaskFromBodyOfWork = async (args, ctx) => {
148
199
  return { result: response.data };
149
200
  };
150
201
  export const activateBodyOfWork = async (args, ctx) => {
151
- const { body_of_work_id } = args;
152
- validateRequired(body_of_work_id, 'body_of_work_id');
153
- validateUUID(body_of_work_id, 'body_of_work_id');
202
+ const { body_of_work_id } = parseArgs(args, activateBodyOfWorkSchema);
154
203
  const apiClient = getApiClient();
155
204
  const response = await apiClient.proxy('activate_body_of_work', { body_of_work_id });
156
205
  if (!response.ok) {
@@ -159,13 +208,7 @@ export const activateBodyOfWork = async (args, ctx) => {
159
208
  return { result: response.data };
160
209
  };
161
210
  export const addTaskDependency = async (args, ctx) => {
162
- const { body_of_work_id, task_id, depends_on_task_id } = args;
163
- validateRequired(body_of_work_id, 'body_of_work_id');
164
- validateUUID(body_of_work_id, 'body_of_work_id');
165
- validateRequired(task_id, 'task_id');
166
- validateUUID(task_id, 'task_id');
167
- validateRequired(depends_on_task_id, 'depends_on_task_id');
168
- validateUUID(depends_on_task_id, 'depends_on_task_id');
211
+ const { body_of_work_id, task_id, depends_on_task_id } = parseArgs(args, addTaskDependencySchema);
169
212
  if (task_id === depends_on_task_id) {
170
213
  throw new Error('A task cannot depend on itself');
171
214
  }
@@ -181,11 +224,7 @@ export const addTaskDependency = async (args, ctx) => {
181
224
  return { result: response.data };
182
225
  };
183
226
  export const removeTaskDependency = async (args, ctx) => {
184
- const { task_id, depends_on_task_id } = args;
185
- validateRequired(task_id, 'task_id');
186
- validateUUID(task_id, 'task_id');
187
- validateRequired(depends_on_task_id, 'depends_on_task_id');
188
- validateUUID(depends_on_task_id, 'depends_on_task_id');
227
+ const { task_id, depends_on_task_id } = parseArgs(args, removeTaskDependencySchema);
189
228
  const apiClient = getApiClient();
190
229
  const response = await apiClient.proxy('remove_task_dependency', {
191
230
  task_id,
@@ -197,14 +236,10 @@ export const removeTaskDependency = async (args, ctx) => {
197
236
  return { result: response.data };
198
237
  };
199
238
  export const getTaskDependencies = async (args, ctx) => {
200
- const { body_of_work_id, task_id } = args;
239
+ const { body_of_work_id, task_id } = parseArgs(args, getTaskDependenciesSchema);
201
240
  if (!body_of_work_id && !task_id) {
202
241
  throw new Error('Either body_of_work_id or task_id is required');
203
242
  }
204
- if (body_of_work_id)
205
- validateUUID(body_of_work_id, 'body_of_work_id');
206
- if (task_id)
207
- validateUUID(task_id, 'task_id');
208
243
  const apiClient = getApiClient();
209
244
  const response = await apiClient.proxy('get_task_dependencies', {
210
245
  body_of_work_id,
@@ -216,9 +251,7 @@ export const getTaskDependencies = async (args, ctx) => {
216
251
  return { result: response.data };
217
252
  };
218
253
  export const getNextBodyOfWorkTask = async (args, ctx) => {
219
- const { body_of_work_id } = args;
220
- validateRequired(body_of_work_id, 'body_of_work_id');
221
- validateUUID(body_of_work_id, 'body_of_work_id');
254
+ const { body_of_work_id } = parseArgs(args, getNextBodyOfWorkTaskSchema);
222
255
  const apiClient = getApiClient();
223
256
  const response = await apiClient.proxy('get_next_body_of_work_task', { body_of_work_id });
224
257
  if (!response.ok) {
@@ -9,20 +9,55 @@
9
9
  * - delete_cost_alert
10
10
  * - get_task_costs
11
11
  */
12
+ import { parseArgs, uuidValidator, createEnumValidator, ValidationError } from '../validators.js';
12
13
  import { getApiClient } from '../api-client.js';
14
+ const VALID_PERIODS = ['daily', 'weekly', 'monthly'];
15
+ const VALID_ALERT_TYPES = ['warning', 'critical'];
16
+ // Argument schemas for type-safe parsing
17
+ const getCostSummarySchema = {
18
+ project_id: { type: 'string', required: true, validate: uuidValidator },
19
+ period: { type: 'string', default: 'daily', validate: createEnumValidator(VALID_PERIODS) },
20
+ limit: { type: 'number', default: 30 },
21
+ };
22
+ const getCostAlertsSchema = {
23
+ project_id: { type: 'string', validate: uuidValidator },
24
+ };
25
+ const addCostAlertSchema = {
26
+ project_id: { type: 'string', validate: uuidValidator },
27
+ threshold_amount: { type: 'number', required: true },
28
+ threshold_period: { type: 'string', required: true, validate: createEnumValidator(VALID_PERIODS) },
29
+ alert_type: { type: 'string', default: 'warning', validate: createEnumValidator(VALID_ALERT_TYPES) },
30
+ };
31
+ const updateCostAlertSchema = {
32
+ alert_id: { type: 'string', required: true, validate: uuidValidator },
33
+ threshold_amount: { type: 'number' },
34
+ threshold_period: { type: 'string', validate: createEnumValidator(VALID_PERIODS) },
35
+ alert_type: { type: 'string', validate: createEnumValidator(VALID_ALERT_TYPES) },
36
+ enabled: { type: 'boolean' },
37
+ };
38
+ const deleteCostAlertSchema = {
39
+ alert_id: { type: 'string', required: true, validate: uuidValidator },
40
+ };
41
+ const getTaskCostsSchema = {
42
+ project_id: { type: 'string', required: true, validate: uuidValidator },
43
+ limit: { type: 'number', default: 20 },
44
+ };
45
+ // Custom validator for positive numbers
46
+ function validatePositiveNumber(value, fieldName) {
47
+ if (value !== undefined && value <= 0) {
48
+ throw new ValidationError(`${fieldName} must be a positive number`, { field: fieldName });
49
+ }
50
+ }
13
51
  /**
14
52
  * Get cost summary for a project (daily, weekly, or monthly)
15
53
  */
16
- export const getCostSummary = async (args, ctx) => {
17
- const { project_id, period = 'daily', limit = 30 } = args;
18
- if (!project_id) {
19
- return {
20
- result: { error: 'project_id is required' },
21
- isError: true,
22
- };
23
- }
54
+ export const getCostSummary = async (args, _ctx) => {
55
+ const { project_id, period, limit } = parseArgs(args, getCostSummarySchema);
24
56
  const apiClient = getApiClient();
25
- const response = await apiClient.getCostSummary(project_id, { period, limit });
57
+ const response = await apiClient.getCostSummary(project_id, {
58
+ period: period,
59
+ limit
60
+ });
26
61
  if (!response.ok) {
27
62
  return {
28
63
  result: { error: response.error || 'Failed to get cost summary' },
@@ -34,8 +69,8 @@ export const getCostSummary = async (args, ctx) => {
34
69
  /**
35
70
  * Get cost alerts for the current user
36
71
  */
37
- export const getCostAlerts = async (args, ctx) => {
38
- const { project_id } = args;
72
+ export const getCostAlerts = async (args, _ctx) => {
73
+ const { project_id } = parseArgs(args, getCostAlertsSchema);
39
74
  const apiClient = getApiClient();
40
75
  const response = await apiClient.getCostAlerts();
41
76
  if (!response.ok) {
@@ -49,26 +84,16 @@ export const getCostAlerts = async (args, ctx) => {
49
84
  /**
50
85
  * Add a cost alert
51
86
  */
52
- export const addCostAlert = async (args, ctx) => {
53
- const { project_id, threshold_amount, threshold_period, alert_type = 'warning', } = args;
54
- if (!threshold_amount || threshold_amount <= 0) {
55
- return {
56
- result: { error: 'threshold_amount must be a positive number' },
57
- isError: true,
58
- };
59
- }
60
- if (!threshold_period || !['daily', 'weekly', 'monthly'].includes(threshold_period)) {
61
- return {
62
- result: { error: 'threshold_period must be "daily", "weekly", or "monthly"' },
63
- isError: true,
64
- };
65
- }
87
+ export const addCostAlert = async (args, _ctx) => {
88
+ const { project_id, threshold_amount, threshold_period, alert_type } = parseArgs(args, addCostAlertSchema);
89
+ // Additional validation for positive amount
90
+ validatePositiveNumber(threshold_amount, 'threshold_amount');
66
91
  const apiClient = getApiClient();
67
92
  const response = await apiClient.addCostAlert({
68
93
  project_id,
69
- threshold_amount,
70
- threshold_period,
71
- alert_type
94
+ threshold_amount: threshold_amount,
95
+ threshold_period: threshold_period,
96
+ alert_type: alert_type
72
97
  });
73
98
  if (!response.ok) {
74
99
  return {
@@ -81,11 +106,12 @@ export const addCostAlert = async (args, ctx) => {
81
106
  /**
82
107
  * Update a cost alert
83
108
  */
84
- export const updateCostAlert = async (args, ctx) => {
85
- const { alert_id, threshold_amount, threshold_period, alert_type, enabled, } = args;
86
- if (!alert_id) {
109
+ export const updateCostAlert = async (args, _ctx) => {
110
+ const { alert_id, threshold_amount, threshold_period, alert_type, enabled } = parseArgs(args, updateCostAlertSchema);
111
+ // Check that at least one update is provided
112
+ if (threshold_amount === undefined && threshold_period === undefined && alert_type === undefined && enabled === undefined) {
87
113
  return {
88
- result: { error: 'alert_id is required' },
114
+ result: { error: 'No updates provided' },
89
115
  isError: true,
90
116
  };
91
117
  }
@@ -98,12 +124,6 @@ export const updateCostAlert = async (args, ctx) => {
98
124
  updates.alert_type = alert_type;
99
125
  if (enabled !== undefined)
100
126
  updates.enabled = enabled;
101
- if (Object.keys(updates).length === 0) {
102
- return {
103
- result: { error: 'No updates provided' },
104
- isError: true,
105
- };
106
- }
107
127
  const apiClient = getApiClient();
108
128
  const response = await apiClient.updateCostAlert(alert_id, updates);
109
129
  if (!response.ok) {
@@ -117,14 +137,8 @@ export const updateCostAlert = async (args, ctx) => {
117
137
  /**
118
138
  * Delete a cost alert
119
139
  */
120
- export const deleteCostAlert = async (args, ctx) => {
121
- const { alert_id } = args;
122
- if (!alert_id) {
123
- return {
124
- result: { error: 'alert_id is required' },
125
- isError: true,
126
- };
127
- }
140
+ export const deleteCostAlert = async (args, _ctx) => {
141
+ const { alert_id } = parseArgs(args, deleteCostAlertSchema);
128
142
  const apiClient = getApiClient();
129
143
  const response = await apiClient.deleteCostAlert(alert_id);
130
144
  if (!response.ok) {
@@ -138,14 +152,8 @@ export const deleteCostAlert = async (args, ctx) => {
138
152
  /**
139
153
  * Get task costs for a project
140
154
  */
141
- export const getTaskCosts = async (args, ctx) => {
142
- const { project_id, limit = 20 } = args;
143
- if (!project_id) {
144
- return {
145
- result: { error: 'project_id is required' },
146
- isError: true,
147
- };
148
- }
155
+ export const getTaskCosts = async (args, _ctx) => {
156
+ const { project_id, limit } = parseArgs(args, getTaskCostsSchema);
149
157
  const apiClient = getApiClient();
150
158
  const response = await apiClient.getTaskCosts(project_id, limit);
151
159
  if (!response.ok) {
@@ -8,33 +8,48 @@
8
8
  *
9
9
  * MIGRATED: Uses Vibescope API client instead of direct Supabase
10
10
  */
11
- import { validateRequired, validateUUID } from '../validators.js';
11
+ import { parseArgs, uuidValidator } from '../validators.js';
12
12
  import { getApiClient } from '../api-client.js';
13
+ // Argument schemas for type-safe parsing
14
+ const logDecisionSchema = {
15
+ project_id: { type: 'string', required: true, validate: uuidValidator },
16
+ title: { type: 'string', required: true },
17
+ description: { type: 'string', required: true },
18
+ rationale: { type: 'string' },
19
+ alternatives_considered: { type: 'array' },
20
+ };
21
+ const getDecisionsSchema = {
22
+ project_id: { type: 'string', required: true, validate: uuidValidator },
23
+ limit: { type: 'number', default: 50 },
24
+ offset: { type: 'number', default: 0 },
25
+ search_query: { type: 'string' },
26
+ };
27
+ const deleteDecisionSchema = {
28
+ decision_id: { type: 'string', required: true, validate: uuidValidator },
29
+ };
13
30
  export const logDecision = async (args, ctx) => {
14
- const { project_id, title, description, rationale, alternatives_considered } = args;
15
- validateRequired(project_id, 'project_id');
16
- validateUUID(project_id, 'project_id');
17
- validateRequired(title, 'title');
18
- validateRequired(description, 'description');
31
+ const { project_id, title, description, rationale, alternatives_considered } = parseArgs(args, logDecisionSchema);
19
32
  const { session } = ctx;
20
33
  const apiClient = getApiClient();
21
34
  const response = await apiClient.logDecision(project_id, {
22
35
  title,
23
36
  description,
24
37
  rationale,
25
- alternatives_considered
38
+ alternatives_considered: alternatives_considered
26
39
  }, session.currentSessionId || undefined);
27
40
  if (!response.ok) {
28
41
  throw new Error(`Failed to log decision: ${response.error}`);
29
42
  }
30
43
  return { result: { success: true, title, decision_id: response.data?.decision_id } };
31
44
  };
32
- export const getDecisions = async (args, ctx) => {
33
- const { project_id } = args;
34
- validateRequired(project_id, 'project_id');
35
- validateUUID(project_id, 'project_id');
45
+ export const getDecisions = async (args, _ctx) => {
46
+ const { project_id, limit, offset, search_query } = parseArgs(args, getDecisionsSchema);
36
47
  const apiClient = getApiClient();
37
- const response = await apiClient.getDecisions(project_id);
48
+ const response = await apiClient.getDecisions(project_id, {
49
+ limit,
50
+ offset,
51
+ search_query
52
+ });
38
53
  if (!response.ok) {
39
54
  throw new Error(`Failed to fetch decisions: ${response.error}`);
40
55
  }
@@ -44,10 +59,8 @@ export const getDecisions = async (args, ctx) => {
44
59
  },
45
60
  };
46
61
  };
47
- export const deleteDecision = async (args, ctx) => {
48
- const { decision_id } = args;
49
- validateRequired(decision_id, 'decision_id');
50
- validateUUID(decision_id, 'decision_id');
62
+ export const deleteDecision = async (args, _ctx) => {
63
+ const { decision_id } = parseArgs(args, deleteDecisionSchema);
51
64
  const apiClient = getApiClient();
52
65
  const response = await apiClient.deleteDecision(decision_id);
53
66
  if (!response.ok) {