@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.
- package/dist/api-client.d.ts +64 -1
- package/dist/api-client.js +34 -3
- package/dist/handlers/bodies-of-work.js +82 -49
- package/dist/handlers/cost.js +62 -54
- package/dist/handlers/decisions.js +29 -16
- package/dist/handlers/deployment.js +112 -106
- package/dist/handlers/discovery.js +35 -5
- package/dist/handlers/fallback.js +24 -19
- package/dist/handlers/file-checkouts.d.ts +18 -0
- package/dist/handlers/file-checkouts.js +101 -0
- package/dist/handlers/findings.d.ts +6 -0
- package/dist/handlers/findings.js +85 -30
- package/dist/handlers/git-issues.js +36 -32
- package/dist/handlers/ideas.js +44 -26
- package/dist/handlers/index.d.ts +2 -0
- package/dist/handlers/index.js +6 -0
- package/dist/handlers/milestones.js +34 -27
- package/dist/handlers/organizations.js +86 -78
- package/dist/handlers/progress.js +22 -11
- package/dist/handlers/project.js +62 -22
- package/dist/handlers/requests.js +15 -11
- package/dist/handlers/roles.d.ts +18 -0
- package/dist/handlers/roles.js +130 -0
- package/dist/handlers/session.js +30 -8
- package/dist/handlers/sprints.js +76 -64
- package/dist/handlers/tasks.js +113 -73
- package/dist/handlers/validation.js +18 -14
- package/dist/tools.js +387 -0
- package/package.json +1 -1
- package/src/api-client.ts +89 -6
- package/src/handlers/__test-setup__.ts +7 -0
- package/src/handlers/bodies-of-work.ts +101 -101
- package/src/handlers/cost.test.ts +34 -44
- package/src/handlers/cost.ts +77 -92
- package/src/handlers/decisions.test.ts +3 -2
- package/src/handlers/decisions.ts +32 -27
- package/src/handlers/deployment.ts +142 -190
- package/src/handlers/discovery.test.ts +4 -5
- package/src/handlers/discovery.ts +37 -6
- package/src/handlers/fallback.ts +31 -29
- package/src/handlers/file-checkouts.test.ts +477 -0
- package/src/handlers/file-checkouts.ts +127 -0
- package/src/handlers/findings.test.ts +145 -0
- package/src/handlers/findings.ts +101 -64
- package/src/handlers/git-issues.ts +40 -80
- package/src/handlers/ideas.ts +56 -54
- package/src/handlers/index.ts +6 -0
- package/src/handlers/milestones.test.ts +1 -1
- package/src/handlers/milestones.ts +47 -45
- package/src/handlers/organizations.ts +104 -129
- package/src/handlers/progress.ts +24 -22
- package/src/handlers/project.ts +89 -57
- package/src/handlers/requests.ts +18 -14
- package/src/handlers/roles.test.ts +303 -0
- package/src/handlers/roles.ts +208 -0
- package/src/handlers/session.ts +39 -17
- package/src/handlers/sprints.ts +96 -129
- package/src/handlers/tasks.ts +144 -138
- package/src/handlers/validation.test.ts +1 -1
- package/src/handlers/validation.ts +20 -22
- package/src/tools.ts +387 -0
- package/dist/config/tool-categories.d.ts +0 -31
- package/dist/config/tool-categories.js +0 -253
- package/dist/knowledge.d.ts +0 -6
- package/dist/knowledge.js +0 -218
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
getFindingsStats,
|
|
6
6
|
updateFinding,
|
|
7
7
|
deleteFinding,
|
|
8
|
+
queryKnowledgeBase,
|
|
8
9
|
} from './findings.js';
|
|
9
10
|
import { ValidationError } from '../validators.js';
|
|
10
11
|
import { createMockContext } from './__test-utils__.js';
|
|
@@ -472,3 +473,147 @@ describe('getFindingsStats', () => {
|
|
|
472
473
|
).rejects.toThrow('Query failed');
|
|
473
474
|
});
|
|
474
475
|
});
|
|
476
|
+
|
|
477
|
+
// ============================================================================
|
|
478
|
+
// queryKnowledgeBase Tests
|
|
479
|
+
// ============================================================================
|
|
480
|
+
|
|
481
|
+
describe('queryKnowledgeBase', () => {
|
|
482
|
+
beforeEach(() => vi.clearAllMocks());
|
|
483
|
+
|
|
484
|
+
it('should throw error for missing project_id', async () => {
|
|
485
|
+
const ctx = createMockContext();
|
|
486
|
+
|
|
487
|
+
await expect(queryKnowledgeBase({}, ctx)).rejects.toThrow(ValidationError);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
491
|
+
const ctx = createMockContext();
|
|
492
|
+
|
|
493
|
+
await expect(
|
|
494
|
+
queryKnowledgeBase({ project_id: 'invalid' }, ctx)
|
|
495
|
+
).rejects.toThrow(ValidationError);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('should query with default parameters', async () => {
|
|
499
|
+
mockApiClient.queryKnowledgeBase.mockResolvedValue({
|
|
500
|
+
ok: true,
|
|
501
|
+
data: {
|
|
502
|
+
findings: [],
|
|
503
|
+
decisions: [],
|
|
504
|
+
completed_tasks: [],
|
|
505
|
+
resolved_blockers: [],
|
|
506
|
+
},
|
|
507
|
+
});
|
|
508
|
+
const ctx = createMockContext();
|
|
509
|
+
|
|
510
|
+
const result = await queryKnowledgeBase({ project_id: VALID_UUID }, ctx);
|
|
511
|
+
|
|
512
|
+
expect(result.result).toMatchObject({
|
|
513
|
+
findings: [],
|
|
514
|
+
decisions: [],
|
|
515
|
+
});
|
|
516
|
+
expect(mockApiClient.queryKnowledgeBase).toHaveBeenCalledWith(
|
|
517
|
+
VALID_UUID,
|
|
518
|
+
expect.objectContaining({
|
|
519
|
+
scope: 'summary',
|
|
520
|
+
limit: 5,
|
|
521
|
+
})
|
|
522
|
+
);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('should pass scope parameter', async () => {
|
|
526
|
+
mockApiClient.queryKnowledgeBase.mockResolvedValue({
|
|
527
|
+
ok: true,
|
|
528
|
+
data: { findings: [] },
|
|
529
|
+
});
|
|
530
|
+
const ctx = createMockContext();
|
|
531
|
+
|
|
532
|
+
await queryKnowledgeBase({ project_id: VALID_UUID, scope: 'detailed' }, ctx);
|
|
533
|
+
|
|
534
|
+
expect(mockApiClient.queryKnowledgeBase).toHaveBeenCalledWith(
|
|
535
|
+
VALID_UUID,
|
|
536
|
+
expect.objectContaining({ scope: 'detailed' })
|
|
537
|
+
);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it('should pass categories filter', async () => {
|
|
541
|
+
mockApiClient.queryKnowledgeBase.mockResolvedValue({
|
|
542
|
+
ok: true,
|
|
543
|
+
data: { findings: [], decisions: [] },
|
|
544
|
+
});
|
|
545
|
+
const ctx = createMockContext();
|
|
546
|
+
|
|
547
|
+
await queryKnowledgeBase({
|
|
548
|
+
project_id: VALID_UUID,
|
|
549
|
+
categories: ['findings', 'decisions']
|
|
550
|
+
}, ctx);
|
|
551
|
+
|
|
552
|
+
expect(mockApiClient.queryKnowledgeBase).toHaveBeenCalledWith(
|
|
553
|
+
VALID_UUID,
|
|
554
|
+
expect.objectContaining({
|
|
555
|
+
categories: ['findings', 'decisions']
|
|
556
|
+
})
|
|
557
|
+
);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it('should cap limit at 20', async () => {
|
|
561
|
+
mockApiClient.queryKnowledgeBase.mockResolvedValue({
|
|
562
|
+
ok: true,
|
|
563
|
+
data: { findings: [] },
|
|
564
|
+
});
|
|
565
|
+
const ctx = createMockContext();
|
|
566
|
+
|
|
567
|
+
await queryKnowledgeBase({ project_id: VALID_UUID, limit: 100 }, ctx);
|
|
568
|
+
|
|
569
|
+
expect(mockApiClient.queryKnowledgeBase).toHaveBeenCalledWith(
|
|
570
|
+
VALID_UUID,
|
|
571
|
+
expect.objectContaining({ limit: 20 })
|
|
572
|
+
);
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it('should enforce minimum limit of 1', async () => {
|
|
576
|
+
mockApiClient.queryKnowledgeBase.mockResolvedValue({
|
|
577
|
+
ok: true,
|
|
578
|
+
data: { findings: [] },
|
|
579
|
+
});
|
|
580
|
+
const ctx = createMockContext();
|
|
581
|
+
|
|
582
|
+
await queryKnowledgeBase({ project_id: VALID_UUID, limit: -5 }, ctx);
|
|
583
|
+
|
|
584
|
+
expect(mockApiClient.queryKnowledgeBase).toHaveBeenCalledWith(
|
|
585
|
+
VALID_UUID,
|
|
586
|
+
expect.objectContaining({ limit: 1 })
|
|
587
|
+
);
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
it('should pass search_query', async () => {
|
|
591
|
+
mockApiClient.queryKnowledgeBase.mockResolvedValue({
|
|
592
|
+
ok: true,
|
|
593
|
+
data: { findings: [] },
|
|
594
|
+
});
|
|
595
|
+
const ctx = createMockContext();
|
|
596
|
+
|
|
597
|
+
await queryKnowledgeBase({
|
|
598
|
+
project_id: VALID_UUID,
|
|
599
|
+
search_query: 'security'
|
|
600
|
+
}, ctx);
|
|
601
|
+
|
|
602
|
+
expect(mockApiClient.queryKnowledgeBase).toHaveBeenCalledWith(
|
|
603
|
+
VALID_UUID,
|
|
604
|
+
expect.objectContaining({ search_query: 'security' })
|
|
605
|
+
);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it('should throw error when API call fails', async () => {
|
|
609
|
+
mockApiClient.queryKnowledgeBase.mockResolvedValue({
|
|
610
|
+
ok: false,
|
|
611
|
+
error: 'Query failed',
|
|
612
|
+
});
|
|
613
|
+
const ctx = createMockContext();
|
|
614
|
+
|
|
615
|
+
await expect(
|
|
616
|
+
queryKnowledgeBase({ project_id: VALID_UUID }, ctx)
|
|
617
|
+
).rejects.toThrow('Query failed');
|
|
618
|
+
});
|
|
619
|
+
});
|
package/src/handlers/findings.ts
CHANGED
|
@@ -10,36 +10,76 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { Handler, HandlerRegistry } from './types.js';
|
|
13
|
-
import {
|
|
13
|
+
import { parseArgs, uuidValidator, createEnumValidator } from '../validators.js';
|
|
14
14
|
import { getApiClient } from '../api-client.js';
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const VALID_FINDING_CATEGORIES = ['performance', 'security', 'code_quality', 'accessibility', 'documentation', 'architecture', 'testing', 'other'] as const;
|
|
17
|
+
const VALID_FINDING_SEVERITIES = ['info', 'low', 'medium', 'high', 'critical'] as const;
|
|
18
|
+
const VALID_FINDING_STATUSES = ['open', 'addressed', 'dismissed', 'wontfix'] as const;
|
|
19
|
+
|
|
20
|
+
type FindingCategory = typeof VALID_FINDING_CATEGORIES[number];
|
|
21
|
+
type FindingSeverity = typeof VALID_FINDING_SEVERITIES[number];
|
|
22
|
+
type FindingStatus = typeof VALID_FINDING_STATUSES[number];
|
|
23
|
+
|
|
24
|
+
// Argument schemas for type-safe parsing
|
|
25
|
+
const addFindingSchema = {
|
|
26
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
27
|
+
title: { type: 'string' as const, required: true as const },
|
|
28
|
+
description: { type: 'string' as const },
|
|
29
|
+
category: { type: 'string' as const, validate: createEnumValidator(VALID_FINDING_CATEGORIES) },
|
|
30
|
+
severity: { type: 'string' as const, validate: createEnumValidator(VALID_FINDING_SEVERITIES) },
|
|
31
|
+
file_path: { type: 'string' as const },
|
|
32
|
+
line_number: { type: 'number' as const },
|
|
33
|
+
related_task_id: { type: 'string' as const, validate: uuidValidator },
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const getFindingsSchema = {
|
|
37
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
38
|
+
category: { type: 'string' as const, validate: createEnumValidator(VALID_FINDING_CATEGORIES) },
|
|
39
|
+
severity: { type: 'string' as const, validate: createEnumValidator(VALID_FINDING_SEVERITIES) },
|
|
40
|
+
status: { type: 'string' as const, validate: createEnumValidator(VALID_FINDING_STATUSES) },
|
|
41
|
+
limit: { type: 'number' as const, default: 50 },
|
|
42
|
+
offset: { type: 'number' as const, default: 0 },
|
|
43
|
+
search_query: { type: 'string' as const },
|
|
44
|
+
summary_only: { type: 'boolean' as const, default: false },
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const getFindingsStatsSchema = {
|
|
48
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const updateFindingSchema = {
|
|
52
|
+
finding_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
53
|
+
title: { type: 'string' as const },
|
|
54
|
+
description: { type: 'string' as const },
|
|
55
|
+
severity: { type: 'string' as const, validate: createEnumValidator(VALID_FINDING_SEVERITIES) },
|
|
56
|
+
status: { type: 'string' as const, validate: createEnumValidator(VALID_FINDING_STATUSES) },
|
|
57
|
+
resolution_note: { type: 'string' as const },
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const deleteFindingSchema = {
|
|
61
|
+
finding_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const VALID_SCOPES = ['summary', 'detailed'] as const;
|
|
65
|
+
|
|
66
|
+
const queryKnowledgeBaseSchema = {
|
|
67
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
68
|
+
scope: { type: 'string' as const, default: 'summary', validate: createEnumValidator(VALID_SCOPES) },
|
|
69
|
+
categories: { type: 'array' as const },
|
|
70
|
+
limit: { type: 'number' as const, default: 5 },
|
|
71
|
+
search_query: { type: 'string' as const },
|
|
72
|
+
};
|
|
19
73
|
|
|
20
74
|
export const addFinding: Handler = async (args, ctx) => {
|
|
21
|
-
const { project_id,
|
|
22
|
-
project_id: string;
|
|
23
|
-
category?: FindingCategory;
|
|
24
|
-
title: string;
|
|
25
|
-
description?: string;
|
|
26
|
-
severity?: FindingSeverity;
|
|
27
|
-
file_path?: string;
|
|
28
|
-
line_number?: number;
|
|
29
|
-
related_task_id?: string;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
validateRequired(project_id, 'project_id');
|
|
33
|
-
validateUUID(project_id, 'project_id');
|
|
34
|
-
validateRequired(title, 'title');
|
|
35
|
-
if (related_task_id) validateUUID(related_task_id, 'related_task_id');
|
|
75
|
+
const { project_id, title, description, category, severity, file_path, line_number, related_task_id } = parseArgs(args, addFindingSchema);
|
|
36
76
|
|
|
37
77
|
const apiClient = getApiClient();
|
|
38
78
|
const response = await apiClient.addFinding(project_id, {
|
|
39
79
|
title,
|
|
40
80
|
description,
|
|
41
|
-
category,
|
|
42
|
-
severity,
|
|
81
|
+
category: category as FindingCategory | undefined,
|
|
82
|
+
severity: severity as FindingSeverity | undefined,
|
|
43
83
|
file_path,
|
|
44
84
|
line_number,
|
|
45
85
|
related_task_id
|
|
@@ -52,26 +92,14 @@ export const addFinding: Handler = async (args, ctx) => {
|
|
|
52
92
|
return { result: response.data };
|
|
53
93
|
};
|
|
54
94
|
|
|
55
|
-
export const getFindings: Handler = async (args,
|
|
56
|
-
const { project_id, category, severity, status, limit
|
|
57
|
-
project_id: string;
|
|
58
|
-
category?: FindingCategory;
|
|
59
|
-
severity?: FindingSeverity;
|
|
60
|
-
status?: FindingStatus;
|
|
61
|
-
limit?: number;
|
|
62
|
-
offset?: number;
|
|
63
|
-
search_query?: string;
|
|
64
|
-
summary_only?: boolean;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
validateRequired(project_id, 'project_id');
|
|
68
|
-
validateUUID(project_id, 'project_id');
|
|
95
|
+
export const getFindings: Handler = async (args, _ctx) => {
|
|
96
|
+
const { project_id, category, severity, status, limit, offset, search_query, summary_only } = parseArgs(args, getFindingsSchema);
|
|
69
97
|
|
|
70
98
|
const apiClient = getApiClient();
|
|
71
99
|
const response = await apiClient.getFindings(project_id, {
|
|
72
|
-
category,
|
|
73
|
-
severity,
|
|
74
|
-
status,
|
|
100
|
+
category: category as FindingCategory | undefined,
|
|
101
|
+
severity: severity as FindingSeverity | undefined,
|
|
102
|
+
status: status as FindingStatus | undefined,
|
|
75
103
|
limit,
|
|
76
104
|
offset,
|
|
77
105
|
search_query,
|
|
@@ -90,13 +118,8 @@ export const getFindings: Handler = async (args, ctx) => {
|
|
|
90
118
|
* Returns counts by category, severity, and status without the actual finding data.
|
|
91
119
|
* This is much more token-efficient than get_findings for understanding the overall state.
|
|
92
120
|
*/
|
|
93
|
-
export const getFindingsStats: Handler = async (args,
|
|
94
|
-
const { project_id } = args
|
|
95
|
-
project_id: string;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
validateRequired(project_id, 'project_id');
|
|
99
|
-
validateUUID(project_id, 'project_id');
|
|
121
|
+
export const getFindingsStats: Handler = async (args, _ctx) => {
|
|
122
|
+
const { project_id } = parseArgs(args, getFindingsStatsSchema);
|
|
100
123
|
|
|
101
124
|
const apiClient = getApiClient();
|
|
102
125
|
const response = await apiClient.getFindingsStats(project_id);
|
|
@@ -108,25 +131,15 @@ export const getFindingsStats: Handler = async (args, ctx) => {
|
|
|
108
131
|
return { result: response.data };
|
|
109
132
|
};
|
|
110
133
|
|
|
111
|
-
export const updateFinding: Handler = async (args,
|
|
112
|
-
const { finding_id,
|
|
113
|
-
finding_id: string;
|
|
114
|
-
status?: FindingStatus;
|
|
115
|
-
resolution_note?: string;
|
|
116
|
-
title?: string;
|
|
117
|
-
description?: string;
|
|
118
|
-
severity?: FindingSeverity;
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
validateRequired(finding_id, 'finding_id');
|
|
122
|
-
validateUUID(finding_id, 'finding_id');
|
|
134
|
+
export const updateFinding: Handler = async (args, _ctx) => {
|
|
135
|
+
const { finding_id, title, description, severity, status, resolution_note } = parseArgs(args, updateFindingSchema);
|
|
123
136
|
|
|
124
137
|
const apiClient = getApiClient();
|
|
125
138
|
const response = await apiClient.updateFinding(finding_id, {
|
|
126
139
|
title,
|
|
127
140
|
description,
|
|
128
|
-
severity,
|
|
129
|
-
status,
|
|
141
|
+
severity: severity as FindingSeverity | undefined,
|
|
142
|
+
status: status as FindingStatus | undefined,
|
|
130
143
|
resolution_note
|
|
131
144
|
});
|
|
132
145
|
|
|
@@ -137,11 +150,8 @@ export const updateFinding: Handler = async (args, ctx) => {
|
|
|
137
150
|
return { result: response.data };
|
|
138
151
|
};
|
|
139
152
|
|
|
140
|
-
export const deleteFinding: Handler = async (args,
|
|
141
|
-
const { finding_id } = args
|
|
142
|
-
|
|
143
|
-
validateRequired(finding_id, 'finding_id');
|
|
144
|
-
validateUUID(finding_id, 'finding_id');
|
|
153
|
+
export const deleteFinding: Handler = async (args, _ctx) => {
|
|
154
|
+
const { finding_id } = parseArgs(args, deleteFindingSchema);
|
|
145
155
|
|
|
146
156
|
const apiClient = getApiClient();
|
|
147
157
|
const response = await apiClient.deleteFinding(finding_id);
|
|
@@ -153,6 +163,32 @@ export const deleteFinding: Handler = async (args, ctx) => {
|
|
|
153
163
|
return { result: response.data };
|
|
154
164
|
};
|
|
155
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Query aggregated project knowledge in a single call.
|
|
168
|
+
* Returns findings, Q&A, decisions, completed tasks, and resolved blockers.
|
|
169
|
+
* Use this instead of multiple separate tool calls to reduce token usage.
|
|
170
|
+
*/
|
|
171
|
+
export const queryKnowledgeBase: Handler = async (args, _ctx) => {
|
|
172
|
+
const { project_id, scope, categories, limit, search_query } = parseArgs(args, queryKnowledgeBaseSchema);
|
|
173
|
+
|
|
174
|
+
// Validate limit range
|
|
175
|
+
const effectiveLimit = Math.min(Math.max(1, limit ?? 5), 20);
|
|
176
|
+
|
|
177
|
+
const apiClient = getApiClient();
|
|
178
|
+
const response = await apiClient.queryKnowledgeBase(project_id, {
|
|
179
|
+
scope: scope as 'summary' | 'detailed' | undefined,
|
|
180
|
+
categories: categories as string[] | undefined,
|
|
181
|
+
limit: effectiveLimit,
|
|
182
|
+
search_query
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
throw new Error(response.error || 'Failed to query knowledge base');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return { result: response.data };
|
|
190
|
+
};
|
|
191
|
+
|
|
156
192
|
/**
|
|
157
193
|
* Findings handlers registry
|
|
158
194
|
*/
|
|
@@ -162,4 +198,5 @@ export const findingHandlers: HandlerRegistry = {
|
|
|
162
198
|
get_findings_stats: getFindingsStats,
|
|
163
199
|
update_finding: updateFinding,
|
|
164
200
|
delete_finding: deleteFinding,
|
|
201
|
+
query_knowledge_base: queryKnowledgeBase,
|
|
165
202
|
};
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { Handler, HandlerRegistry } from './types.js';
|
|
12
|
-
import {
|
|
12
|
+
import { parseArgs, uuidValidator, createEnumValidator } from '../validators.js';
|
|
13
13
|
import { getApiClient } from '../api-client.js';
|
|
14
14
|
|
|
15
15
|
const VALID_GIT_ISSUE_TYPES = [
|
|
@@ -25,41 +25,38 @@ const VALID_GIT_ISSUE_STATUSES = ['open', 'resolved'] as const;
|
|
|
25
25
|
type GitIssueType = (typeof VALID_GIT_ISSUE_TYPES)[number];
|
|
26
26
|
type GitIssueStatus = (typeof VALID_GIT_ISSUE_STATUSES)[number];
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
project_id: string;
|
|
40
|
-
issue_type: string;
|
|
41
|
-
branch: string;
|
|
42
|
-
target_branch?: string;
|
|
43
|
-
pr_url?: string;
|
|
44
|
-
conflicting_files?: string[];
|
|
45
|
-
error_message?: string;
|
|
46
|
-
task_id?: string;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
validateRequired(project_id, 'project_id');
|
|
50
|
-
validateUUID(project_id, 'project_id');
|
|
51
|
-
validateRequired(issue_type, 'issue_type');
|
|
52
|
-
validateRequired(branch, 'branch');
|
|
53
|
-
|
|
54
|
-
if (!VALID_GIT_ISSUE_TYPES.includes(issue_type as GitIssueType)) {
|
|
55
|
-
throw new Error(
|
|
56
|
-
`Invalid issue_type. Valid types: ${VALID_GIT_ISSUE_TYPES.join(', ')}`
|
|
57
|
-
);
|
|
58
|
-
}
|
|
28
|
+
// Argument schemas for type-safe parsing
|
|
29
|
+
const addGitIssueSchema = {
|
|
30
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
31
|
+
issue_type: { type: 'string' as const, required: true as const, validate: createEnumValidator(VALID_GIT_ISSUE_TYPES) },
|
|
32
|
+
branch: { type: 'string' as const, required: true as const },
|
|
33
|
+
target_branch: { type: 'string' as const },
|
|
34
|
+
pr_url: { type: 'string' as const },
|
|
35
|
+
conflicting_files: { type: 'array' as const },
|
|
36
|
+
error_message: { type: 'string' as const },
|
|
37
|
+
task_id: { type: 'string' as const, validate: uuidValidator },
|
|
38
|
+
};
|
|
59
39
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
40
|
+
const resolveGitIssueSchema = {
|
|
41
|
+
git_issue_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
42
|
+
resolution_note: { type: 'string' as const },
|
|
43
|
+
auto_resolved: { type: 'boolean' as const },
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const getGitIssuesSchema = {
|
|
47
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
48
|
+
status: { type: 'string' as const, default: 'open', validate: createEnumValidator(VALID_GIT_ISSUE_STATUSES) },
|
|
49
|
+
issue_type: { type: 'string' as const, validate: createEnumValidator(VALID_GIT_ISSUE_TYPES) },
|
|
50
|
+
branch: { type: 'string' as const },
|
|
51
|
+
limit: { type: 'number' as const, default: 50 },
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const deleteGitIssueSchema = {
|
|
55
|
+
git_issue_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const addGitIssue: Handler = async (args, ctx) => {
|
|
59
|
+
const { project_id, issue_type, branch, target_branch, pr_url, conflicting_files, error_message, task_id } = parseArgs(args, addGitIssueSchema);
|
|
63
60
|
|
|
64
61
|
const apiClient = getApiClient();
|
|
65
62
|
const response = await apiClient.addGitIssue(project_id, {
|
|
@@ -67,7 +64,7 @@ export const addGitIssue: Handler = async (args, ctx) => {
|
|
|
67
64
|
branch,
|
|
68
65
|
target_branch,
|
|
69
66
|
pr_url,
|
|
70
|
-
conflicting_files,
|
|
67
|
+
conflicting_files: conflicting_files as string[] | undefined,
|
|
71
68
|
error_message,
|
|
72
69
|
task_id
|
|
73
70
|
}, ctx.session.currentSessionId || undefined);
|
|
@@ -80,14 +77,7 @@ export const addGitIssue: Handler = async (args, ctx) => {
|
|
|
80
77
|
};
|
|
81
78
|
|
|
82
79
|
export const resolveGitIssue: Handler = async (args, ctx) => {
|
|
83
|
-
const { git_issue_id, resolution_note, auto_resolved } = args
|
|
84
|
-
git_issue_id: string;
|
|
85
|
-
resolution_note?: string;
|
|
86
|
-
auto_resolved?: boolean;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
validateRequired(git_issue_id, 'git_issue_id');
|
|
90
|
-
validateUUID(git_issue_id, 'git_issue_id');
|
|
80
|
+
const { git_issue_id, resolution_note, auto_resolved } = parseArgs(args, resolveGitIssueSchema);
|
|
91
81
|
|
|
92
82
|
const apiClient = getApiClient();
|
|
93
83
|
const response = await apiClient.resolveGitIssue(git_issue_id, {
|
|
@@ -102,40 +92,13 @@ export const resolveGitIssue: Handler = async (args, ctx) => {
|
|
|
102
92
|
return { result: response.data };
|
|
103
93
|
};
|
|
104
94
|
|
|
105
|
-
export const getGitIssues: Handler = async (args,
|
|
106
|
-
const {
|
|
107
|
-
project_id,
|
|
108
|
-
status = 'open',
|
|
109
|
-
issue_type,
|
|
110
|
-
branch,
|
|
111
|
-
limit = 50,
|
|
112
|
-
} = args as {
|
|
113
|
-
project_id: string;
|
|
114
|
-
status?: string;
|
|
115
|
-
issue_type?: string;
|
|
116
|
-
branch?: string;
|
|
117
|
-
limit?: number;
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
validateRequired(project_id, 'project_id');
|
|
121
|
-
validateUUID(project_id, 'project_id');
|
|
122
|
-
|
|
123
|
-
if (status && !VALID_GIT_ISSUE_STATUSES.includes(status as GitIssueStatus)) {
|
|
124
|
-
throw new Error(
|
|
125
|
-
`Invalid status. Valid statuses: ${VALID_GIT_ISSUE_STATUSES.join(', ')}`
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (issue_type && !VALID_GIT_ISSUE_TYPES.includes(issue_type as GitIssueType)) {
|
|
130
|
-
throw new Error(
|
|
131
|
-
`Invalid issue_type. Valid types: ${VALID_GIT_ISSUE_TYPES.join(', ')}`
|
|
132
|
-
);
|
|
133
|
-
}
|
|
95
|
+
export const getGitIssues: Handler = async (args, _ctx) => {
|
|
96
|
+
const { project_id, status, issue_type, branch, limit } = parseArgs(args, getGitIssuesSchema);
|
|
134
97
|
|
|
135
98
|
const apiClient = getApiClient();
|
|
136
99
|
const response = await apiClient.getGitIssues(project_id, {
|
|
137
|
-
status,
|
|
138
|
-
issue_type,
|
|
100
|
+
status: status as GitIssueStatus | undefined,
|
|
101
|
+
issue_type: issue_type as GitIssueType | undefined,
|
|
139
102
|
branch,
|
|
140
103
|
limit
|
|
141
104
|
});
|
|
@@ -147,11 +110,8 @@ export const getGitIssues: Handler = async (args, ctx) => {
|
|
|
147
110
|
return { result: response.data };
|
|
148
111
|
};
|
|
149
112
|
|
|
150
|
-
export const deleteGitIssue: Handler = async (args,
|
|
151
|
-
const { git_issue_id } = args
|
|
152
|
-
|
|
153
|
-
validateRequired(git_issue_id, 'git_issue_id');
|
|
154
|
-
validateUUID(git_issue_id, 'git_issue_id');
|
|
113
|
+
export const deleteGitIssue: Handler = async (args, _ctx) => {
|
|
114
|
+
const { git_issue_id } = parseArgs(args, deleteGitIssueSchema);
|
|
155
115
|
|
|
156
116
|
const apiClient = getApiClient();
|
|
157
117
|
const response = await apiClient.deleteGitIssue(git_issue_id);
|