@vibescope/mcp-server 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +60 -7
  2. package/dist/api-client.d.ts +251 -1
  3. package/dist/api-client.js +82 -3
  4. package/dist/handlers/blockers.js +9 -8
  5. package/dist/handlers/bodies-of-work.js +96 -63
  6. package/dist/handlers/connectors.d.ts +45 -0
  7. package/dist/handlers/connectors.js +183 -0
  8. package/dist/handlers/cost.d.ts +10 -0
  9. package/dist/handlers/cost.js +112 -50
  10. package/dist/handlers/decisions.js +32 -19
  11. package/dist/handlers/deployment.js +144 -122
  12. package/dist/handlers/discovery.d.ts +7 -0
  13. package/dist/handlers/discovery.js +96 -7
  14. package/dist/handlers/fallback.js +29 -23
  15. package/dist/handlers/file-checkouts.d.ts +20 -0
  16. package/dist/handlers/file-checkouts.js +133 -0
  17. package/dist/handlers/findings.d.ts +6 -0
  18. package/dist/handlers/findings.js +96 -40
  19. package/dist/handlers/git-issues.js +40 -36
  20. package/dist/handlers/ideas.js +49 -31
  21. package/dist/handlers/index.d.ts +3 -0
  22. package/dist/handlers/index.js +9 -0
  23. package/dist/handlers/milestones.js +39 -32
  24. package/dist/handlers/organizations.js +99 -91
  25. package/dist/handlers/progress.js +24 -13
  26. package/dist/handlers/project.js +68 -28
  27. package/dist/handlers/requests.js +18 -14
  28. package/dist/handlers/roles.d.ts +18 -0
  29. package/dist/handlers/roles.js +130 -0
  30. package/dist/handlers/session.js +58 -17
  31. package/dist/handlers/sprints.js +93 -81
  32. package/dist/handlers/tasks.d.ts +2 -0
  33. package/dist/handlers/tasks.js +189 -91
  34. package/dist/handlers/types.d.ts +64 -2
  35. package/dist/handlers/types.js +48 -1
  36. package/dist/handlers/validation.js +21 -17
  37. package/dist/index.js +7 -2716
  38. package/dist/token-tracking.d.ts +74 -0
  39. package/dist/token-tracking.js +122 -0
  40. package/dist/tools.js +685 -9
  41. package/dist/utils.d.ts +5 -0
  42. package/dist/utils.js +17 -0
  43. package/docs/TOOLS.md +2053 -0
  44. package/package.json +4 -1
  45. package/scripts/generate-docs.ts +212 -0
  46. package/src/api-client.test.ts +718 -0
  47. package/src/api-client.ts +320 -6
  48. package/src/handlers/__test-setup__.ts +16 -0
  49. package/src/handlers/blockers.test.ts +31 -19
  50. package/src/handlers/blockers.ts +9 -8
  51. package/src/handlers/bodies-of-work.test.ts +55 -32
  52. package/src/handlers/bodies-of-work.ts +115 -115
  53. package/src/handlers/connectors.test.ts +834 -0
  54. package/src/handlers/connectors.ts +229 -0
  55. package/src/handlers/cost.test.ts +34 -44
  56. package/src/handlers/cost.ts +136 -85
  57. package/src/handlers/decisions.test.ts +37 -27
  58. package/src/handlers/decisions.ts +35 -30
  59. package/src/handlers/deployment.ts +180 -208
  60. package/src/handlers/discovery.test.ts +4 -5
  61. package/src/handlers/discovery.ts +98 -8
  62. package/src/handlers/fallback.test.ts +26 -22
  63. package/src/handlers/fallback.ts +36 -33
  64. package/src/handlers/file-checkouts.test.ts +670 -0
  65. package/src/handlers/file-checkouts.ts +165 -0
  66. package/src/handlers/findings.test.ts +178 -19
  67. package/src/handlers/findings.ts +112 -74
  68. package/src/handlers/git-issues.test.ts +51 -43
  69. package/src/handlers/git-issues.ts +44 -84
  70. package/src/handlers/ideas.test.ts +28 -23
  71. package/src/handlers/ideas.ts +61 -59
  72. package/src/handlers/index.ts +9 -0
  73. package/src/handlers/milestones.test.ts +33 -28
  74. package/src/handlers/milestones.ts +52 -50
  75. package/src/handlers/organizations.test.ts +104 -83
  76. package/src/handlers/organizations.ts +117 -142
  77. package/src/handlers/progress.test.ts +20 -14
  78. package/src/handlers/progress.ts +26 -24
  79. package/src/handlers/project.test.ts +34 -27
  80. package/src/handlers/project.ts +95 -63
  81. package/src/handlers/requests.test.ts +27 -18
  82. package/src/handlers/requests.ts +21 -17
  83. package/src/handlers/roles.test.ts +303 -0
  84. package/src/handlers/roles.ts +208 -0
  85. package/src/handlers/session.test.ts +47 -0
  86. package/src/handlers/session.ts +71 -26
  87. package/src/handlers/sprints.test.ts +71 -50
  88. package/src/handlers/sprints.ts +113 -146
  89. package/src/handlers/tasks.test.ts +77 -15
  90. package/src/handlers/tasks.ts +231 -156
  91. package/src/handlers/tool-categories.test.ts +66 -0
  92. package/src/handlers/types.ts +81 -2
  93. package/src/handlers/validation.test.ts +78 -45
  94. package/src/handlers/validation.ts +23 -25
  95. package/src/index.ts +12 -2732
  96. package/src/token-tracking.test.ts +453 -0
  97. package/src/token-tracking.ts +164 -0
  98. package/src/tools.ts +685 -9
  99. package/src/utils.test.ts +2 -2
  100. package/src/utils.ts +17 -0
  101. package/dist/config/tool-categories.d.ts +0 -31
  102. package/dist/config/tool-categories.js +0 -253
  103. package/dist/knowledge.d.ts +0 -6
  104. package/dist/knowledge.js +0 -218
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * MIGRATED: Uses Vibescope API client instead of direct Supabase
11
11
  */
12
- import { validateRequired, validateUUID } from '../validators.js';
12
+ import { parseArgs, uuidValidator, createEnumValidator } from '../validators.js';
13
13
  import { FALLBACK_ACTIVITIES } from '../utils.js';
14
14
  import { getApiClient } from '../api-client.js';
15
15
  const VALID_ACTIVITIES = [
@@ -23,20 +23,32 @@ const VALID_ACTIVITIES = [
23
23
  'documentation_review',
24
24
  'dependency_audit',
25
25
  'validate_completed_tasks',
26
+ 'worktree_cleanup',
26
27
  ];
28
+ // Argument schemas for type-safe parsing
29
+ const startFallbackActivitySchema = {
30
+ project_id: { type: 'string', required: true, validate: uuidValidator },
31
+ activity: { type: 'string', required: true, validate: createEnumValidator(VALID_ACTIVITIES) },
32
+ };
33
+ const stopFallbackActivitySchema = {
34
+ project_id: { type: 'string', required: true, validate: uuidValidator },
35
+ summary: { type: 'string' },
36
+ };
37
+ const getActivityHistorySchema = {
38
+ project_id: { type: 'string', required: true, validate: uuidValidator },
39
+ activity_type: { type: 'string' },
40
+ limit: { type: 'number', default: 50 },
41
+ };
42
+ const getActivitySchedulesSchema = {
43
+ project_id: { type: 'string', required: true, validate: uuidValidator },
44
+ };
27
45
  export const startFallbackActivity = async (args, ctx) => {
28
- const { project_id, activity } = args;
29
- validateRequired(project_id, 'project_id');
30
- validateUUID(project_id, 'project_id');
31
- validateRequired(activity, 'activity');
32
- if (!VALID_ACTIVITIES.includes(activity)) {
33
- throw new Error(`Invalid activity. Must be one of: ${VALID_ACTIVITIES.join(', ')}`);
34
- }
46
+ const { project_id, activity } = parseArgs(args, startFallbackActivitySchema);
35
47
  const { session } = ctx;
36
48
  const apiClient = getApiClient();
37
49
  const response = await apiClient.startFallbackActivity(project_id, activity, session.currentSessionId || undefined);
38
50
  if (!response.ok) {
39
- throw new Error(`Failed to start fallback activity: ${response.error}`);
51
+ return { result: { error: response.error || 'Failed to start fallback activity' }, isError: true };
40
52
  }
41
53
  // Get the activity details for the response
42
54
  const activityInfo = FALLBACK_ACTIVITIES.find((a) => a.activity === activity);
@@ -61,14 +73,12 @@ export const startFallbackActivity = async (args, ctx) => {
61
73
  return { result };
62
74
  };
63
75
  export const stopFallbackActivity = async (args, ctx) => {
64
- const { project_id, summary } = args;
65
- validateRequired(project_id, 'project_id');
66
- validateUUID(project_id, 'project_id');
76
+ const { project_id, summary } = parseArgs(args, stopFallbackActivitySchema);
67
77
  const { session } = ctx;
68
78
  const apiClient = getApiClient();
69
79
  const response = await apiClient.stopFallbackActivity(project_id, summary, session.currentSessionId || undefined);
70
80
  if (!response.ok) {
71
- throw new Error(`Failed to stop fallback activity: ${response.error}`);
81
+ return { result: { error: response.error || 'Failed to stop fallback activity' }, isError: true };
72
82
  }
73
83
  return {
74
84
  result: {
@@ -77,10 +87,8 @@ export const stopFallbackActivity = async (args, ctx) => {
77
87
  },
78
88
  };
79
89
  };
80
- export const getActivityHistory = async (args, ctx) => {
81
- const { project_id, activity_type, limit = 50 } = args;
82
- validateRequired(project_id, 'project_id');
83
- validateUUID(project_id, 'project_id');
90
+ export const getActivityHistory = async (args, _ctx) => {
91
+ const { project_id, activity_type, limit } = parseArgs(args, getActivityHistorySchema);
84
92
  const apiClient = getApiClient();
85
93
  // Use proxy for get_activity_history operation
86
94
  const response = await apiClient.proxy('get_activity_history', {
@@ -89,7 +97,7 @@ export const getActivityHistory = async (args, ctx) => {
89
97
  limit
90
98
  });
91
99
  if (!response.ok) {
92
- throw new Error(`Failed to get activity history: ${response.error}`);
100
+ return { result: { error: response.error || 'Failed to get activity history' }, isError: true };
93
101
  }
94
102
  return {
95
103
  result: {
@@ -99,17 +107,15 @@ export const getActivityHistory = async (args, ctx) => {
99
107
  },
100
108
  };
101
109
  };
102
- export const getActivitySchedules = async (args, ctx) => {
103
- const { project_id } = args;
104
- validateRequired(project_id, 'project_id');
105
- validateUUID(project_id, 'project_id');
110
+ export const getActivitySchedules = async (args, _ctx) => {
111
+ const { project_id } = parseArgs(args, getActivitySchedulesSchema);
106
112
  const apiClient = getApiClient();
107
113
  // Use proxy for get_activity_schedules operation
108
114
  const response = await apiClient.proxy('get_activity_schedules', {
109
115
  project_id
110
116
  });
111
117
  if (!response.ok) {
112
- throw new Error(`Failed to get activity schedules: ${response.error}`);
118
+ return { result: { error: response.error || 'Failed to get activity schedules' }, isError: true };
113
119
  }
114
120
  return {
115
121
  result: {
@@ -0,0 +1,20 @@
1
+ /**
2
+ * File Checkouts Handlers
3
+ *
4
+ * Handles file checkout/checkin for multi-agent coordination:
5
+ * - checkout_file: Check out a file before editing
6
+ * - checkin_file: Check in a file after editing
7
+ * - get_file_checkouts: Get active checkouts for a project
8
+ * - abandon_checkout: Force release a checkout
9
+ * - is_file_available: Check if a file is available for checkout
10
+ */
11
+ import type { Handler, HandlerRegistry } from './types.js';
12
+ export declare const checkoutFile: Handler;
13
+ export declare const checkinFile: Handler;
14
+ export declare const getFileCheckouts: Handler;
15
+ export declare const abandonCheckout: Handler;
16
+ export declare const isFileAvailable: Handler;
17
+ /**
18
+ * File Checkouts handlers registry
19
+ */
20
+ export declare const fileCheckoutHandlers: HandlerRegistry;
@@ -0,0 +1,133 @@
1
+ /**
2
+ * File Checkouts Handlers
3
+ *
4
+ * Handles file checkout/checkin for multi-agent coordination:
5
+ * - checkout_file: Check out a file before editing
6
+ * - checkin_file: Check in a file after editing
7
+ * - get_file_checkouts: Get active checkouts for a project
8
+ * - abandon_checkout: Force release a checkout
9
+ * - is_file_available: Check if a file is available for checkout
10
+ */
11
+ import { parseArgs, uuidValidator, createEnumValidator } from '../validators.js';
12
+ import { getApiClient } from '../api-client.js';
13
+ const VALID_CHECKOUT_STATUSES = ['checked_out', 'checked_in', 'abandoned'];
14
+ // Argument schemas for type-safe parsing
15
+ const checkoutFileSchema = {
16
+ project_id: { type: 'string', required: true, validate: uuidValidator },
17
+ file_path: { type: 'string', required: true },
18
+ reason: { type: 'string' },
19
+ };
20
+ const checkinFileSchema = {
21
+ checkout_id: { type: 'string', validate: uuidValidator },
22
+ project_id: { type: 'string', validate: uuidValidator },
23
+ file_path: { type: 'string' },
24
+ summary: { type: 'string' },
25
+ };
26
+ const getFileCheckoutsSchema = {
27
+ project_id: { type: 'string', required: true, validate: uuidValidator },
28
+ status: { type: 'string', validate: createEnumValidator(VALID_CHECKOUT_STATUSES) },
29
+ file_path: { type: 'string' },
30
+ limit: { type: 'number', default: 50 },
31
+ };
32
+ const abandonCheckoutSchema = {
33
+ checkout_id: { type: 'string', validate: uuidValidator },
34
+ project_id: { type: 'string', validate: uuidValidator },
35
+ file_path: { type: 'string' },
36
+ };
37
+ const isFileAvailableSchema = {
38
+ project_id: { type: 'string', required: true, validate: uuidValidator },
39
+ file_path: { type: 'string', required: true },
40
+ };
41
+ export const checkoutFile = async (args, ctx) => {
42
+ const { project_id, file_path, reason } = parseArgs(args, checkoutFileSchema);
43
+ const apiClient = getApiClient();
44
+ const response = await apiClient.checkoutFile(project_id, file_path, reason, ctx.session.currentSessionId || undefined);
45
+ if (!response.ok) {
46
+ return { result: { error: response.error || 'Failed to checkout file' }, isError: true };
47
+ }
48
+ return { result: response.data };
49
+ };
50
+ export const checkinFile = async (args, ctx) => {
51
+ const { checkout_id, project_id, file_path, summary } = parseArgs(args, checkinFileSchema);
52
+ // Validate that either checkout_id or both project_id and file_path are provided
53
+ if (!checkout_id && (!project_id || !file_path)) {
54
+ return { result: { error: 'Either checkout_id or both project_id and file_path are required' }, isError: true };
55
+ }
56
+ const apiClient = getApiClient();
57
+ const response = await apiClient.checkinFile({
58
+ checkout_id,
59
+ project_id,
60
+ file_path,
61
+ summary
62
+ }, ctx.session.currentSessionId || undefined);
63
+ if (!response.ok) {
64
+ return { result: { error: response.error || 'Failed to checkin file' }, isError: true };
65
+ }
66
+ return { result: response.data };
67
+ };
68
+ export const getFileCheckouts = async (args, _ctx) => {
69
+ const { project_id, status, file_path, limit } = parseArgs(args, getFileCheckoutsSchema);
70
+ const apiClient = getApiClient();
71
+ const response = await apiClient.getFileCheckouts(project_id, {
72
+ status,
73
+ file_path,
74
+ limit
75
+ });
76
+ if (!response.ok) {
77
+ return { result: { error: response.error || 'Failed to get file checkouts' }, isError: true };
78
+ }
79
+ return { result: response.data };
80
+ };
81
+ export const abandonCheckout = async (args, _ctx) => {
82
+ const { checkout_id, project_id, file_path } = parseArgs(args, abandonCheckoutSchema);
83
+ // Validate that either checkout_id or both project_id and file_path are provided
84
+ if (!checkout_id && (!project_id || !file_path)) {
85
+ return { result: { error: 'Either checkout_id or both project_id and file_path are required' }, isError: true };
86
+ }
87
+ const apiClient = getApiClient();
88
+ const response = await apiClient.abandonCheckout({
89
+ checkout_id,
90
+ project_id,
91
+ file_path
92
+ });
93
+ if (!response.ok) {
94
+ return { result: { error: response.error || 'Failed to abandon checkout' }, isError: true };
95
+ }
96
+ return { result: response.data };
97
+ };
98
+ export const isFileAvailable = async (args, _ctx) => {
99
+ const { project_id, file_path } = parseArgs(args, isFileAvailableSchema);
100
+ const apiClient = getApiClient();
101
+ const response = await apiClient.getFileCheckouts(project_id, {
102
+ status: 'checked_out',
103
+ file_path,
104
+ limit: 1
105
+ });
106
+ if (!response.ok) {
107
+ return { result: { error: response.error || 'Failed to check file availability' }, isError: true };
108
+ }
109
+ const checkouts = response.data?.checkouts || [];
110
+ const activeCheckout = checkouts.length > 0 ? checkouts[0] : null;
111
+ return {
112
+ result: {
113
+ available: !activeCheckout,
114
+ file_path,
115
+ checked_out_by: activeCheckout ? {
116
+ checkout_id: activeCheckout.id,
117
+ checked_out_by: activeCheckout.checked_out_by,
118
+ checked_out_at: activeCheckout.checked_out_at,
119
+ reason: activeCheckout.checkout_reason
120
+ } : null
121
+ }
122
+ };
123
+ };
124
+ /**
125
+ * File Checkouts handlers registry
126
+ */
127
+ export const fileCheckoutHandlers = {
128
+ checkout_file: checkoutFile,
129
+ checkin_file: checkinFile,
130
+ get_file_checkouts: getFileCheckouts,
131
+ abandon_checkout: abandonCheckout,
132
+ is_file_available: isFileAvailable,
133
+ };
@@ -19,6 +19,12 @@ export declare const getFindings: Handler;
19
19
  export declare const getFindingsStats: Handler;
20
20
  export declare const updateFinding: Handler;
21
21
  export declare const deleteFinding: Handler;
22
+ /**
23
+ * Query aggregated project knowledge in a single call.
24
+ * Returns findings, Q&A, decisions, completed tasks, and resolved blockers.
25
+ * Use this instead of multiple separate tool calls to reduce token usage.
26
+ */
27
+ export declare const queryKnowledgeBase: Handler;
22
28
  /**
23
29
  * Findings handlers registry
24
30
  */
@@ -8,92 +8,147 @@
8
8
  * - update_finding
9
9
  * - delete_finding
10
10
  */
11
- import { validateRequired, validateUUID } from '../validators.js';
11
+ import { success, error } from './types.js';
12
+ import { parseArgs, uuidValidator, createEnumValidator } from '../validators.js';
12
13
  import { getApiClient } from '../api-client.js';
14
+ const VALID_FINDING_CATEGORIES = ['performance', 'security', 'code_quality', 'accessibility', 'documentation', 'architecture', 'testing', 'other'];
15
+ const VALID_FINDING_SEVERITIES = ['info', 'low', 'medium', 'high', 'critical'];
16
+ const VALID_FINDING_STATUSES = ['open', 'addressed', 'dismissed', 'wontfix'];
17
+ // Argument schemas for type-safe parsing
18
+ const addFindingSchema = {
19
+ project_id: { type: 'string', required: true, validate: uuidValidator },
20
+ title: { type: 'string', required: true },
21
+ description: { type: 'string' },
22
+ category: { type: 'string', validate: createEnumValidator(VALID_FINDING_CATEGORIES) },
23
+ severity: { type: 'string', validate: createEnumValidator(VALID_FINDING_SEVERITIES) },
24
+ file_path: { type: 'string' },
25
+ line_number: { type: 'number' },
26
+ related_task_id: { type: 'string', validate: uuidValidator },
27
+ };
28
+ const getFindingsSchema = {
29
+ project_id: { type: 'string', required: true, validate: uuidValidator },
30
+ category: { type: 'string', validate: createEnumValidator(VALID_FINDING_CATEGORIES) },
31
+ severity: { type: 'string', validate: createEnumValidator(VALID_FINDING_SEVERITIES) },
32
+ status: { type: 'string', validate: createEnumValidator(VALID_FINDING_STATUSES) },
33
+ limit: { type: 'number', default: 50 },
34
+ offset: { type: 'number', default: 0 },
35
+ search_query: { type: 'string' },
36
+ summary_only: { type: 'boolean', default: false },
37
+ };
38
+ const getFindingsStatsSchema = {
39
+ project_id: { type: 'string', required: true, validate: uuidValidator },
40
+ };
41
+ const updateFindingSchema = {
42
+ finding_id: { type: 'string', required: true, validate: uuidValidator },
43
+ title: { type: 'string' },
44
+ description: { type: 'string' },
45
+ severity: { type: 'string', validate: createEnumValidator(VALID_FINDING_SEVERITIES) },
46
+ status: { type: 'string', validate: createEnumValidator(VALID_FINDING_STATUSES) },
47
+ resolution_note: { type: 'string' },
48
+ };
49
+ const deleteFindingSchema = {
50
+ finding_id: { type: 'string', required: true, validate: uuidValidator },
51
+ };
52
+ const VALID_SCOPES = ['summary', 'detailed'];
53
+ const queryKnowledgeBaseSchema = {
54
+ project_id: { type: 'string', required: true, validate: uuidValidator },
55
+ scope: { type: 'string', default: 'summary', validate: createEnumValidator(VALID_SCOPES) },
56
+ categories: { type: 'array' },
57
+ limit: { type: 'number', default: 5 },
58
+ search_query: { type: 'string' },
59
+ };
13
60
  export const addFinding = async (args, ctx) => {
14
- const { project_id, category, title, description, severity, file_path, line_number, related_task_id } = args;
15
- validateRequired(project_id, 'project_id');
16
- validateUUID(project_id, 'project_id');
17
- validateRequired(title, 'title');
18
- if (related_task_id)
19
- validateUUID(related_task_id, 'related_task_id');
61
+ const { project_id, title, description, category, severity, file_path, line_number, related_task_id } = parseArgs(args, addFindingSchema);
20
62
  const apiClient = getApiClient();
21
63
  const response = await apiClient.addFinding(project_id, {
22
64
  title,
23
65
  description,
24
- category,
25
- severity,
66
+ category: category,
67
+ severity: severity,
26
68
  file_path,
27
69
  line_number,
28
70
  related_task_id
29
71
  }, ctx.session.currentSessionId || undefined);
30
72
  if (!response.ok) {
31
- throw new Error(response.error || 'Failed to add finding');
73
+ return error(response.error || 'Failed to add finding');
32
74
  }
33
- return { result: response.data };
75
+ return success(response.data);
34
76
  };
35
- export const getFindings = async (args, ctx) => {
36
- const { project_id, category, severity, status, limit = 50, offset = 0, search_query, summary_only = false } = args;
37
- validateRequired(project_id, 'project_id');
38
- validateUUID(project_id, 'project_id');
77
+ export const getFindings = async (args, _ctx) => {
78
+ const { project_id, category, severity, status, limit, offset, search_query, summary_only } = parseArgs(args, getFindingsSchema);
39
79
  const apiClient = getApiClient();
40
80
  const response = await apiClient.getFindings(project_id, {
41
- category,
42
- severity,
43
- status,
81
+ category: category,
82
+ severity: severity,
83
+ status: status,
44
84
  limit,
45
85
  offset,
46
86
  search_query,
47
87
  summary_only
48
88
  });
49
89
  if (!response.ok) {
50
- throw new Error(response.error || 'Failed to get findings');
90
+ return error(response.error || 'Failed to get findings');
51
91
  }
52
- return { result: response.data };
92
+ return success(response.data);
53
93
  };
54
94
  /**
55
95
  * Get aggregate statistics about findings for a project.
56
96
  * Returns counts by category, severity, and status without the actual finding data.
57
97
  * This is much more token-efficient than get_findings for understanding the overall state.
58
98
  */
59
- export const getFindingsStats = async (args, ctx) => {
60
- const { project_id } = args;
61
- validateRequired(project_id, 'project_id');
62
- validateUUID(project_id, 'project_id');
99
+ export const getFindingsStats = async (args, _ctx) => {
100
+ const { project_id } = parseArgs(args, getFindingsStatsSchema);
63
101
  const apiClient = getApiClient();
64
102
  const response = await apiClient.getFindingsStats(project_id);
65
103
  if (!response.ok) {
66
- throw new Error(response.error || 'Failed to get findings stats');
104
+ return error(response.error || 'Failed to get findings stats');
67
105
  }
68
- return { result: response.data };
106
+ return success(response.data);
69
107
  };
70
- export const updateFinding = async (args, ctx) => {
71
- const { finding_id, status, resolution_note, title, description, severity } = args;
72
- validateRequired(finding_id, 'finding_id');
73
- validateUUID(finding_id, 'finding_id');
108
+ export const updateFinding = async (args, _ctx) => {
109
+ const { finding_id, title, description, severity, status, resolution_note } = parseArgs(args, updateFindingSchema);
74
110
  const apiClient = getApiClient();
75
111
  const response = await apiClient.updateFinding(finding_id, {
76
112
  title,
77
113
  description,
78
- severity,
79
- status,
114
+ severity: severity,
115
+ status: status,
80
116
  resolution_note
81
117
  });
82
118
  if (!response.ok) {
83
- throw new Error(response.error || 'Failed to update finding');
119
+ return error(response.error || 'Failed to update finding');
84
120
  }
85
- return { result: response.data };
121
+ return success(response.data);
86
122
  };
87
- export const deleteFinding = async (args, ctx) => {
88
- const { finding_id } = args;
89
- validateRequired(finding_id, 'finding_id');
90
- validateUUID(finding_id, 'finding_id');
123
+ export const deleteFinding = async (args, _ctx) => {
124
+ const { finding_id } = parseArgs(args, deleteFindingSchema);
91
125
  const apiClient = getApiClient();
92
126
  const response = await apiClient.deleteFinding(finding_id);
93
127
  if (!response.ok) {
94
- throw new Error(response.error || 'Failed to delete finding');
128
+ return error(response.error || 'Failed to delete finding');
129
+ }
130
+ return success(response.data);
131
+ };
132
+ /**
133
+ * Query aggregated project knowledge in a single call.
134
+ * Returns findings, Q&A, decisions, completed tasks, and resolved blockers.
135
+ * Use this instead of multiple separate tool calls to reduce token usage.
136
+ */
137
+ export const queryKnowledgeBase = async (args, _ctx) => {
138
+ const { project_id, scope, categories, limit, search_query } = parseArgs(args, queryKnowledgeBaseSchema);
139
+ // Validate limit range
140
+ const effectiveLimit = Math.min(Math.max(1, limit ?? 5), 20);
141
+ const apiClient = getApiClient();
142
+ const response = await apiClient.queryKnowledgeBase(project_id, {
143
+ scope: scope,
144
+ categories: categories,
145
+ limit: effectiveLimit,
146
+ search_query
147
+ });
148
+ if (!response.ok) {
149
+ return error(response.error || 'Failed to query knowledge base');
95
150
  }
96
- return { result: response.data };
151
+ return success(response.data);
97
152
  };
98
153
  /**
99
154
  * Findings handlers registry
@@ -104,4 +159,5 @@ export const findingHandlers = {
104
159
  get_findings_stats: getFindingsStats,
105
160
  update_finding: updateFinding,
106
161
  delete_finding: deleteFinding,
162
+ query_knowledge_base: queryKnowledgeBase,
107
163
  };
@@ -7,7 +7,7 @@
7
7
  * - get_git_issues: List git issues for a project
8
8
  * - delete_git_issue: Remove a git issue
9
9
  */
10
- import { validateRequired, validateUUID } from '../validators.js';
10
+ import { parseArgs, uuidValidator, createEnumValidator } from '../validators.js';
11
11
  import { getApiClient } from '../api-client.js';
12
12
  const VALID_GIT_ISSUE_TYPES = [
13
13
  'merge_conflict',
@@ -17,77 +17,81 @@ const VALID_GIT_ISSUE_TYPES = [
17
17
  'pr_not_mergeable',
18
18
  ];
19
19
  const VALID_GIT_ISSUE_STATUSES = ['open', 'resolved'];
20
+ // Argument schemas for type-safe parsing
21
+ const addGitIssueSchema = {
22
+ project_id: { type: 'string', required: true, validate: uuidValidator },
23
+ issue_type: { type: 'string', required: true, validate: createEnumValidator(VALID_GIT_ISSUE_TYPES) },
24
+ branch: { type: 'string', required: true },
25
+ target_branch: { type: 'string' },
26
+ pr_url: { type: 'string' },
27
+ conflicting_files: { type: 'array' },
28
+ error_message: { type: 'string' },
29
+ task_id: { type: 'string', validate: uuidValidator },
30
+ };
31
+ const resolveGitIssueSchema = {
32
+ git_issue_id: { type: 'string', required: true, validate: uuidValidator },
33
+ resolution_note: { type: 'string' },
34
+ auto_resolved: { type: 'boolean' },
35
+ };
36
+ const getGitIssuesSchema = {
37
+ project_id: { type: 'string', required: true, validate: uuidValidator },
38
+ status: { type: 'string', default: 'open', validate: createEnumValidator(VALID_GIT_ISSUE_STATUSES) },
39
+ issue_type: { type: 'string', validate: createEnumValidator(VALID_GIT_ISSUE_TYPES) },
40
+ branch: { type: 'string' },
41
+ limit: { type: 'number', default: 50 },
42
+ };
43
+ const deleteGitIssueSchema = {
44
+ git_issue_id: { type: 'string', required: true, validate: uuidValidator },
45
+ };
20
46
  export const addGitIssue = async (args, ctx) => {
21
- const { project_id, issue_type, branch, target_branch, pr_url, conflicting_files, error_message, task_id, } = args;
22
- validateRequired(project_id, 'project_id');
23
- validateUUID(project_id, 'project_id');
24
- validateRequired(issue_type, 'issue_type');
25
- validateRequired(branch, 'branch');
26
- if (!VALID_GIT_ISSUE_TYPES.includes(issue_type)) {
27
- throw new Error(`Invalid issue_type. Valid types: ${VALID_GIT_ISSUE_TYPES.join(', ')}`);
28
- }
29
- if (task_id) {
30
- validateUUID(task_id, 'task_id');
31
- }
47
+ const { project_id, issue_type, branch, target_branch, pr_url, conflicting_files, error_message, task_id } = parseArgs(args, addGitIssueSchema);
32
48
  const apiClient = getApiClient();
33
49
  const response = await apiClient.addGitIssue(project_id, {
34
50
  issue_type: issue_type,
35
51
  branch,
36
52
  target_branch,
37
53
  pr_url,
38
- conflicting_files,
54
+ conflicting_files: conflicting_files,
39
55
  error_message,
40
56
  task_id
41
57
  }, ctx.session.currentSessionId || undefined);
42
58
  if (!response.ok) {
43
- throw new Error(response.error || 'Failed to add git issue');
59
+ return { result: { error: response.error || 'Failed to add git issue' }, isError: true };
44
60
  }
45
61
  return { result: response.data };
46
62
  };
47
63
  export const resolveGitIssue = async (args, ctx) => {
48
- const { git_issue_id, resolution_note, auto_resolved } = args;
49
- validateRequired(git_issue_id, 'git_issue_id');
50
- validateUUID(git_issue_id, 'git_issue_id');
64
+ const { git_issue_id, resolution_note, auto_resolved } = parseArgs(args, resolveGitIssueSchema);
51
65
  const apiClient = getApiClient();
52
66
  const response = await apiClient.resolveGitIssue(git_issue_id, {
53
67
  resolution_note,
54
68
  auto_resolved
55
69
  }, ctx.session.currentSessionId || undefined);
56
70
  if (!response.ok) {
57
- throw new Error(response.error || 'Failed to resolve git issue');
71
+ return { result: { error: response.error || 'Failed to resolve git issue' }, isError: true };
58
72
  }
59
73
  return { result: response.data };
60
74
  };
61
- export const getGitIssues = async (args, ctx) => {
62
- const { project_id, status = 'open', issue_type, branch, limit = 50, } = args;
63
- validateRequired(project_id, 'project_id');
64
- validateUUID(project_id, 'project_id');
65
- if (status && !VALID_GIT_ISSUE_STATUSES.includes(status)) {
66
- throw new Error(`Invalid status. Valid statuses: ${VALID_GIT_ISSUE_STATUSES.join(', ')}`);
67
- }
68
- if (issue_type && !VALID_GIT_ISSUE_TYPES.includes(issue_type)) {
69
- throw new Error(`Invalid issue_type. Valid types: ${VALID_GIT_ISSUE_TYPES.join(', ')}`);
70
- }
75
+ export const getGitIssues = async (args, _ctx) => {
76
+ const { project_id, status, issue_type, branch, limit } = parseArgs(args, getGitIssuesSchema);
71
77
  const apiClient = getApiClient();
72
78
  const response = await apiClient.getGitIssues(project_id, {
73
- status,
74
- issue_type,
79
+ status: status,
80
+ issue_type: issue_type,
75
81
  branch,
76
82
  limit
77
83
  });
78
84
  if (!response.ok) {
79
- throw new Error(response.error || 'Failed to fetch git issues');
85
+ return { result: { error: response.error || 'Failed to fetch git issues' }, isError: true };
80
86
  }
81
87
  return { result: response.data };
82
88
  };
83
- export const deleteGitIssue = async (args, ctx) => {
84
- const { git_issue_id } = args;
85
- validateRequired(git_issue_id, 'git_issue_id');
86
- validateUUID(git_issue_id, 'git_issue_id');
89
+ export const deleteGitIssue = async (args, _ctx) => {
90
+ const { git_issue_id } = parseArgs(args, deleteGitIssueSchema);
87
91
  const apiClient = getApiClient();
88
92
  const response = await apiClient.deleteGitIssue(git_issue_id);
89
93
  if (!response.ok) {
90
- throw new Error(response.error || 'Failed to delete git issue');
94
+ return { result: { error: response.error || 'Failed to delete git issue' }, isError: true };
91
95
  }
92
96
  return { result: response.data };
93
97
  };