@vibescope/mcp-server 0.2.8 → 0.3.0

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 (97) hide show
  1. package/CHANGELOG.md +84 -84
  2. package/README.md +194 -194
  3. package/dist/api-client.d.ts +41 -5
  4. package/dist/api-client.js +34 -0
  5. package/dist/cli.d.ts +1 -1
  6. package/dist/cli.js +30 -38
  7. package/dist/handlers/discovery.js +2 -0
  8. package/dist/handlers/roles.js +1 -8
  9. package/dist/handlers/session.d.ts +11 -0
  10. package/dist/handlers/session.js +124 -32
  11. package/dist/handlers/tasks.d.ts +8 -0
  12. package/dist/handlers/tasks.js +163 -3
  13. package/dist/handlers/tool-docs.js +840 -828
  14. package/dist/handlers/validation.js +71 -15
  15. package/dist/index.js +73 -73
  16. package/dist/setup.js +6 -6
  17. package/dist/templates/agent-guidelines.js +185 -185
  18. package/dist/templates/help-content.d.ts +24 -0
  19. package/dist/templates/help-content.js +1728 -0
  20. package/dist/tools.js +132 -87
  21. package/dist/utils.d.ts +15 -11
  22. package/dist/utils.js +53 -28
  23. package/docs/TOOLS.md +2406 -2053
  24. package/package.json +1 -1
  25. package/scripts/generate-docs.ts +212 -212
  26. package/scripts/version-bump.ts +203 -203
  27. package/src/api-client.test.ts +723 -723
  28. package/src/api-client.ts +2561 -2499
  29. package/src/cli.test.ts +24 -8
  30. package/src/cli.ts +204 -212
  31. package/src/handlers/__test-setup__.ts +236 -236
  32. package/src/handlers/__test-utils__.ts +87 -87
  33. package/src/handlers/blockers.test.ts +468 -468
  34. package/src/handlers/blockers.ts +163 -163
  35. package/src/handlers/bodies-of-work.test.ts +704 -704
  36. package/src/handlers/bodies-of-work.ts +526 -526
  37. package/src/handlers/connectors.test.ts +834 -834
  38. package/src/handlers/connectors.ts +229 -229
  39. package/src/handlers/cost.test.ts +462 -462
  40. package/src/handlers/cost.ts +285 -285
  41. package/src/handlers/decisions.test.ts +382 -382
  42. package/src/handlers/decisions.ts +153 -153
  43. package/src/handlers/deployment.test.ts +551 -551
  44. package/src/handlers/deployment.ts +541 -541
  45. package/src/handlers/discovery.test.ts +206 -206
  46. package/src/handlers/discovery.ts +392 -390
  47. package/src/handlers/fallback.test.ts +537 -537
  48. package/src/handlers/fallback.ts +194 -194
  49. package/src/handlers/file-checkouts.test.ts +750 -750
  50. package/src/handlers/file-checkouts.ts +185 -185
  51. package/src/handlers/findings.test.ts +633 -633
  52. package/src/handlers/findings.ts +239 -239
  53. package/src/handlers/git-issues.test.ts +631 -631
  54. package/src/handlers/git-issues.ts +136 -136
  55. package/src/handlers/ideas.test.ts +644 -644
  56. package/src/handlers/ideas.ts +207 -207
  57. package/src/handlers/index.ts +84 -84
  58. package/src/handlers/milestones.test.ts +475 -475
  59. package/src/handlers/milestones.ts +180 -180
  60. package/src/handlers/organizations.test.ts +826 -826
  61. package/src/handlers/organizations.ts +315 -315
  62. package/src/handlers/progress.test.ts +269 -269
  63. package/src/handlers/progress.ts +77 -77
  64. package/src/handlers/project.test.ts +546 -546
  65. package/src/handlers/project.ts +239 -239
  66. package/src/handlers/requests.test.ts +303 -303
  67. package/src/handlers/requests.ts +99 -99
  68. package/src/handlers/roles.test.ts +305 -303
  69. package/src/handlers/roles.ts +219 -226
  70. package/src/handlers/session.test.ts +998 -875
  71. package/src/handlers/session.ts +839 -738
  72. package/src/handlers/sprints.test.ts +732 -732
  73. package/src/handlers/sprints.ts +537 -537
  74. package/src/handlers/tasks.test.ts +931 -907
  75. package/src/handlers/tasks.ts +1121 -945
  76. package/src/handlers/tool-categories.test.ts +66 -66
  77. package/src/handlers/tool-docs.ts +1109 -1096
  78. package/src/handlers/types.test.ts +259 -259
  79. package/src/handlers/types.ts +175 -175
  80. package/src/handlers/validation.test.ts +582 -582
  81. package/src/handlers/validation.ts +159 -97
  82. package/src/index.test.ts +674 -0
  83. package/src/index.ts +792 -792
  84. package/src/setup.test.ts +233 -233
  85. package/src/setup.ts +404 -403
  86. package/src/templates/agent-guidelines.ts +210 -210
  87. package/src/templates/help-content.ts +1751 -0
  88. package/src/token-tracking.test.ts +463 -463
  89. package/src/token-tracking.ts +166 -166
  90. package/src/tools.test.ts +416 -0
  91. package/src/tools.ts +3607 -3562
  92. package/src/utils.test.ts +785 -683
  93. package/src/utils.ts +469 -436
  94. package/src/validators.test.ts +223 -223
  95. package/src/validators.ts +249 -249
  96. package/tsconfig.json +16 -16
  97. package/vitest.config.ts +14 -14
@@ -1,546 +1,546 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import {
3
- getProjectContext,
4
- getGitWorkflow,
5
- createProject,
6
- updateProject,
7
- updateProjectReadme,
8
- } from './project.js';
9
- import { ValidationError } from '../validators.js';
10
- import { createMockContext } from './__test-utils__.js';
11
- import { mockApiClient } from './__test-setup__.js';
12
-
13
- const VALID_UUID = '123e4567-e89b-12d3-a456-426614174000';
14
-
15
- // ============================================================================
16
- // getProjectContext Tests
17
- // ============================================================================
18
-
19
- describe('getProjectContext', () => {
20
- beforeEach(() => vi.clearAllMocks());
21
-
22
- it('should list all projects when no project_id or git_url', async () => {
23
- const mockProjects = [
24
- { id: 'proj-1', name: 'Project 1', description: 'Desc 1', status: 'active', git_url: null },
25
- { id: 'proj-2', name: 'Project 2', description: 'Desc 2', status: 'active', git_url: 'https://github.com/test' },
26
- ];
27
-
28
- mockApiClient.listProjects.mockResolvedValue({
29
- ok: true,
30
- data: { projects: mockProjects },
31
- });
32
- const ctx = createMockContext();
33
-
34
- const result = await getProjectContext({}, ctx);
35
-
36
- expect(result.result).toHaveProperty('projects');
37
- expect((result.result as { projects: unknown[] }).projects).toEqual(mockProjects);
38
- expect(mockApiClient.listProjects).toHaveBeenCalled();
39
- });
40
-
41
- it('should return found: false when project not found', async () => {
42
- mockApiClient.getProject.mockResolvedValue({
43
- ok: true,
44
- data: {
45
- found: false,
46
- message: 'Project not found. Use create_project to create one.',
47
- },
48
- });
49
- const ctx = createMockContext();
50
-
51
- const result = await getProjectContext(
52
- { project_id: VALID_UUID },
53
- ctx
54
- );
55
-
56
- expect(result.result).toMatchObject({
57
- found: false,
58
- message: 'Project not found. Use create_project to create one.',
59
- });
60
- });
61
-
62
- it('should find project by git_url', async () => {
63
- mockApiClient.getProject.mockResolvedValue({
64
- ok: true,
65
- data: {
66
- found: true,
67
- project: {
68
- id: 'proj-1',
69
- name: 'Test Project',
70
- description: null,
71
- goal: null,
72
- status: 'active',
73
- git_url: 'https://github.com/test',
74
- agent_instructions: null,
75
- tech_stack: null,
76
- },
77
- },
78
- });
79
- const ctx = createMockContext();
80
-
81
- const result = await getProjectContext(
82
- { git_url: 'https://github.com/test' },
83
- ctx
84
- );
85
-
86
- expect(result.result).toMatchObject({
87
- found: true,
88
- });
89
- expect(mockApiClient.getProject).toHaveBeenCalledWith(
90
- 'by-git-url',
91
- 'https://github.com/test'
92
- );
93
- });
94
-
95
- it('should include active_tasks in result', async () => {
96
- mockApiClient.getProject.mockResolvedValue({
97
- ok: true,
98
- data: {
99
- found: true,
100
- project: {
101
- id: 'proj-1',
102
- name: 'Test Project',
103
- description: null,
104
- goal: null,
105
- status: 'active',
106
- git_url: null,
107
- agent_instructions: null,
108
- tech_stack: null,
109
- },
110
- active_tasks: [],
111
- },
112
- });
113
- const ctx = createMockContext();
114
-
115
- const result = await getProjectContext(
116
- { project_id: VALID_UUID },
117
- ctx
118
- );
119
-
120
- expect(result.result).toHaveProperty('active_tasks');
121
- });
122
-
123
- it('should return error when API call fails', async () => {
124
- mockApiClient.listProjects.mockResolvedValue({
125
- ok: false,
126
- error: 'Database error',
127
- });
128
- const ctx = createMockContext();
129
-
130
- const result = await getProjectContext({}, ctx);
131
-
132
- expect(result.isError).toBe(true);
133
- expect(result.result).toMatchObject({ error: 'Database error' });
134
- });
135
- });
136
-
137
- // ============================================================================
138
- // getGitWorkflow Tests
139
- // ============================================================================
140
-
141
- describe('getGitWorkflow', () => {
142
- beforeEach(() => vi.clearAllMocks());
143
-
144
- it('should throw error for missing project_id', async () => {
145
- const ctx = createMockContext();
146
-
147
- await expect(getGitWorkflow({}, ctx)).rejects.toThrow(ValidationError);
148
- });
149
-
150
- it('should throw error for invalid project_id UUID', async () => {
151
- const ctx = createMockContext();
152
-
153
- await expect(
154
- getGitWorkflow({ project_id: 'invalid' }, ctx)
155
- ).rejects.toThrow(ValidationError);
156
- });
157
-
158
- it('should return workflow instructions for github-flow', async () => {
159
- mockApiClient.getGitWorkflow.mockResolvedValue({
160
- ok: true,
161
- data: {
162
- workflow: 'github-flow',
163
- main_branch: 'main',
164
- develop_branch: null,
165
- auto_branch: true,
166
- auto_tag: false,
167
- git_url: 'https://github.com/test',
168
- instructions: [
169
- 'Create feature branch from main',
170
- 'Make changes and commit',
171
- 'Create PR to main',
172
- ],
173
- },
174
- });
175
- const ctx = createMockContext();
176
-
177
- const result = await getGitWorkflow(
178
- { project_id: VALID_UUID },
179
- ctx
180
- );
181
-
182
- expect(result.result).toMatchObject({
183
- workflow: 'github-flow',
184
- main_branch: 'main',
185
- auto_branch: true,
186
- });
187
- expect((result.result as { instructions: string[] }).instructions.length).toBeGreaterThan(0);
188
- });
189
-
190
- it('should return workflow instructions for git-flow', async () => {
191
- mockApiClient.getGitWorkflow.mockResolvedValue({
192
- ok: true,
193
- data: {
194
- workflow: 'git-flow',
195
- main_branch: 'main',
196
- develop_branch: 'develop',
197
- auto_branch: false,
198
- auto_tag: true,
199
- git_url: 'https://github.com/test',
200
- instructions: ['Create feature branch from develop'],
201
- },
202
- });
203
- const ctx = createMockContext();
204
-
205
- const result = await getGitWorkflow(
206
- { project_id: VALID_UUID },
207
- ctx
208
- );
209
-
210
- expect(result.result).toMatchObject({
211
- workflow: 'git-flow',
212
- main_branch: 'main',
213
- develop_branch: 'develop',
214
- });
215
- });
216
-
217
- it('should return no workflow instructions when workflow is none', async () => {
218
- mockApiClient.getGitWorkflow.mockResolvedValue({
219
- ok: true,
220
- data: {
221
- workflow: 'none',
222
- main_branch: 'main',
223
- develop_branch: null,
224
- auto_branch: false,
225
- auto_tag: false,
226
- git_url: null,
227
- instructions: ['No git workflow configured for this project.'],
228
- },
229
- });
230
- const ctx = createMockContext();
231
-
232
- const result = await getGitWorkflow(
233
- { project_id: VALID_UUID },
234
- ctx
235
- );
236
-
237
- expect(result.result).toMatchObject({
238
- workflow: 'none',
239
- });
240
- expect((result.result as { instructions: string[] }).instructions[0]).toContain('No git workflow configured');
241
- });
242
-
243
- it('should return error when project not found', async () => {
244
- mockApiClient.getGitWorkflow.mockResolvedValue({
245
- ok: false,
246
- error: 'Project not found',
247
- });
248
- const ctx = createMockContext();
249
-
250
- const result = await getGitWorkflow({ project_id: VALID_UUID }, ctx);
251
-
252
- expect(result.isError).toBe(true);
253
- expect(result.result).toMatchObject({ error: 'Project not found' });
254
- });
255
- });
256
-
257
- // ============================================================================
258
- // createProject Tests
259
- // ============================================================================
260
-
261
- describe('createProject', () => {
262
- beforeEach(() => vi.clearAllMocks());
263
-
264
- it('should create project successfully with minimal args', async () => {
265
- const mockProject = {
266
- id: 'new-proj-1',
267
- name: 'New Project',
268
- description: null,
269
- goal: null,
270
- git_url: null,
271
- tech_stack: null,
272
- };
273
-
274
- mockApiClient.createProject.mockResolvedValue({
275
- ok: true,
276
- data: {
277
- success: true,
278
- project: mockProject,
279
- },
280
- });
281
- const ctx = createMockContext();
282
-
283
- const result = await createProject({ name: 'New Project' }, ctx);
284
-
285
- expect(result.result).toMatchObject({
286
- success: true,
287
- project: mockProject,
288
- });
289
- expect(mockApiClient.createProject).toHaveBeenCalledWith({
290
- name: 'New Project',
291
- description: undefined,
292
- goal: undefined,
293
- git_url: undefined,
294
- tech_stack: undefined,
295
- });
296
- });
297
-
298
- it('should create project with all optional fields', async () => {
299
- const mockProject = {
300
- id: 'new-proj-2',
301
- name: 'Full Project',
302
- description: 'A test project',
303
- goal: 'Complete the test',
304
- git_url: 'https://github.com/test/repo',
305
- tech_stack: ['TypeScript', 'Svelte'],
306
- };
307
-
308
- mockApiClient.createProject.mockResolvedValue({
309
- ok: true,
310
- data: {
311
- success: true,
312
- project: mockProject,
313
- },
314
- });
315
- const ctx = createMockContext();
316
-
317
- const result = await createProject(
318
- {
319
- name: 'Full Project',
320
- description: 'A test project',
321
- goal: 'Complete the test',
322
- git_url: 'https://github.com/test/repo',
323
- tech_stack: ['TypeScript', 'Svelte'],
324
- },
325
- ctx
326
- );
327
-
328
- expect(result.result).toMatchObject({
329
- success: true,
330
- project: mockProject,
331
- });
332
- });
333
-
334
- it('should return error when insert fails', async () => {
335
- mockApiClient.createProject.mockResolvedValue({
336
- ok: false,
337
- error: 'Failed to create project',
338
- });
339
- const ctx = createMockContext();
340
-
341
- const result = await createProject({ name: 'Test Project' }, ctx);
342
-
343
- expect(result.isError).toBe(true);
344
- expect(result.result).toMatchObject({ error: 'Failed to create project' });
345
- });
346
-
347
- it('should throw error when name is missing', async () => {
348
- const ctx = createMockContext();
349
-
350
- await expect(
351
- createProject({}, ctx)
352
- ).rejects.toThrow(ValidationError);
353
- });
354
- });
355
-
356
- // ============================================================================
357
- // updateProject Tests
358
- // ============================================================================
359
-
360
- describe('updateProject', () => {
361
- beforeEach(() => vi.clearAllMocks());
362
-
363
- it('should throw error for missing project_id', async () => {
364
- const ctx = createMockContext();
365
-
366
- await expect(updateProject({}, ctx)).rejects.toThrow(ValidationError);
367
- });
368
-
369
- it('should throw error for invalid project_id UUID', async () => {
370
- const ctx = createMockContext();
371
-
372
- await expect(
373
- updateProject({ project_id: 'invalid' }, ctx)
374
- ).rejects.toThrow(ValidationError);
375
- });
376
-
377
- it('should throw error for invalid status', async () => {
378
- const ctx = createMockContext();
379
-
380
- await expect(
381
- updateProject({
382
- project_id: VALID_UUID,
383
- status: 'invalid_status',
384
- }, ctx)
385
- ).rejects.toThrow(ValidationError);
386
- });
387
-
388
- it('should update project successfully', async () => {
389
- mockApiClient.updateProject.mockResolvedValue({
390
- ok: true,
391
- data: {
392
- success: true,
393
- project_id: VALID_UUID,
394
- },
395
- });
396
- const ctx = createMockContext();
397
-
398
- const result = await updateProject(
399
- {
400
- project_id: VALID_UUID,
401
- name: 'Updated Name',
402
- status: 'active',
403
- },
404
- ctx
405
- );
406
-
407
- expect(result.result).toMatchObject({
408
- success: true,
409
- project_id: VALID_UUID,
410
- });
411
- expect(mockApiClient.updateProject).toHaveBeenCalledWith(
412
- VALID_UUID,
413
- {
414
- name: 'Updated Name',
415
- status: 'active',
416
- }
417
- );
418
- });
419
-
420
- it('should update git workflow settings', async () => {
421
- mockApiClient.updateProject.mockResolvedValue({
422
- ok: true,
423
- data: {
424
- success: true,
425
- project_id: VALID_UUID,
426
- },
427
- });
428
- const ctx = createMockContext();
429
-
430
- const result = await updateProject(
431
- {
432
- project_id: VALID_UUID,
433
- git_workflow: 'github-flow',
434
- git_main_branch: 'main',
435
- git_auto_branch: true,
436
- },
437
- ctx
438
- );
439
-
440
- expect(result.result).toMatchObject({
441
- success: true,
442
- });
443
- expect(mockApiClient.updateProject).toHaveBeenCalledWith(
444
- VALID_UUID,
445
- {
446
- git_workflow: 'github-flow',
447
- git_main_branch: 'main',
448
- git_auto_branch: true,
449
- }
450
- );
451
- });
452
-
453
- it('should return error when update fails', async () => {
454
- mockApiClient.updateProject.mockResolvedValue({
455
- ok: false,
456
- error: 'Failed to update project',
457
- });
458
- const ctx = createMockContext();
459
-
460
- const result = await updateProject({
461
- project_id: VALID_UUID,
462
- name: 'Test',
463
- }, ctx);
464
-
465
- expect(result.isError).toBe(true);
466
- expect(result.result).toMatchObject({ error: 'Failed to update project' });
467
- });
468
- });
469
-
470
- // ============================================================================
471
- // updateProjectReadme Tests
472
- // ============================================================================
473
-
474
- describe('updateProjectReadme', () => {
475
- beforeEach(() => vi.clearAllMocks());
476
-
477
- it('should throw error for missing project_id', async () => {
478
- const ctx = createMockContext();
479
-
480
- await expect(
481
- updateProjectReadme({ readme_content: '# README' }, ctx)
482
- ).rejects.toThrow(ValidationError);
483
- });
484
-
485
- it('should throw error for missing readme_content', async () => {
486
- const ctx = createMockContext();
487
-
488
- await expect(
489
- updateProjectReadme({ project_id: VALID_UUID }, ctx)
490
- ).rejects.toThrow(ValidationError);
491
- });
492
-
493
- it('should throw error for invalid project_id UUID', async () => {
494
- const ctx = createMockContext();
495
-
496
- await expect(
497
- updateProjectReadme({
498
- project_id: 'invalid',
499
- readme_content: '# README',
500
- }, ctx)
501
- ).rejects.toThrow(ValidationError);
502
- });
503
-
504
- it('should update readme successfully', async () => {
505
- mockApiClient.updateProjectReadme.mockResolvedValue({
506
- ok: true,
507
- data: {
508
- success: true,
509
- project_id: VALID_UUID,
510
- },
511
- });
512
- const ctx = createMockContext();
513
-
514
- const result = await updateProjectReadme(
515
- {
516
- project_id: VALID_UUID,
517
- readme_content: '# My Project\n\nDescription here.',
518
- },
519
- ctx
520
- );
521
-
522
- expect(result.result).toMatchObject({
523
- success: true,
524
- });
525
- expect(mockApiClient.updateProjectReadme).toHaveBeenCalledWith(
526
- VALID_UUID,
527
- '# My Project\n\nDescription here.'
528
- );
529
- });
530
-
531
- it('should return error when update fails', async () => {
532
- mockApiClient.updateProjectReadme.mockResolvedValue({
533
- ok: false,
534
- error: 'Failed to update README',
535
- });
536
- const ctx = createMockContext();
537
-
538
- const result = await updateProjectReadme({
539
- project_id: VALID_UUID,
540
- readme_content: '# README',
541
- }, ctx);
542
-
543
- expect(result.isError).toBe(true);
544
- expect(result.result).toMatchObject({ error: 'Failed to update README' });
545
- });
546
- });
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import {
3
+ getProjectContext,
4
+ getGitWorkflow,
5
+ createProject,
6
+ updateProject,
7
+ updateProjectReadme,
8
+ } from './project.js';
9
+ import { ValidationError } from '../validators.js';
10
+ import { createMockContext } from './__test-utils__.js';
11
+ import { mockApiClient } from './__test-setup__.js';
12
+
13
+ const VALID_UUID = '123e4567-e89b-12d3-a456-426614174000';
14
+
15
+ // ============================================================================
16
+ // getProjectContext Tests
17
+ // ============================================================================
18
+
19
+ describe('getProjectContext', () => {
20
+ beforeEach(() => vi.clearAllMocks());
21
+
22
+ it('should list all projects when no project_id or git_url', async () => {
23
+ const mockProjects = [
24
+ { id: 'proj-1', name: 'Project 1', description: 'Desc 1', status: 'active', git_url: null },
25
+ { id: 'proj-2', name: 'Project 2', description: 'Desc 2', status: 'active', git_url: 'https://github.com/test' },
26
+ ];
27
+
28
+ mockApiClient.listProjects.mockResolvedValue({
29
+ ok: true,
30
+ data: { projects: mockProjects },
31
+ });
32
+ const ctx = createMockContext();
33
+
34
+ const result = await getProjectContext({}, ctx);
35
+
36
+ expect(result.result).toHaveProperty('projects');
37
+ expect((result.result as { projects: unknown[] }).projects).toEqual(mockProjects);
38
+ expect(mockApiClient.listProjects).toHaveBeenCalled();
39
+ });
40
+
41
+ it('should return found: false when project not found', async () => {
42
+ mockApiClient.getProject.mockResolvedValue({
43
+ ok: true,
44
+ data: {
45
+ found: false,
46
+ message: 'Project not found. Use create_project to create one.',
47
+ },
48
+ });
49
+ const ctx = createMockContext();
50
+
51
+ const result = await getProjectContext(
52
+ { project_id: VALID_UUID },
53
+ ctx
54
+ );
55
+
56
+ expect(result.result).toMatchObject({
57
+ found: false,
58
+ message: 'Project not found. Use create_project to create one.',
59
+ });
60
+ });
61
+
62
+ it('should find project by git_url', async () => {
63
+ mockApiClient.getProject.mockResolvedValue({
64
+ ok: true,
65
+ data: {
66
+ found: true,
67
+ project: {
68
+ id: 'proj-1',
69
+ name: 'Test Project',
70
+ description: null,
71
+ goal: null,
72
+ status: 'active',
73
+ git_url: 'https://github.com/test',
74
+ agent_instructions: null,
75
+ tech_stack: null,
76
+ },
77
+ },
78
+ });
79
+ const ctx = createMockContext();
80
+
81
+ const result = await getProjectContext(
82
+ { git_url: 'https://github.com/test' },
83
+ ctx
84
+ );
85
+
86
+ expect(result.result).toMatchObject({
87
+ found: true,
88
+ });
89
+ expect(mockApiClient.getProject).toHaveBeenCalledWith(
90
+ 'by-git-url',
91
+ 'https://github.com/test'
92
+ );
93
+ });
94
+
95
+ it('should include active_tasks in result', async () => {
96
+ mockApiClient.getProject.mockResolvedValue({
97
+ ok: true,
98
+ data: {
99
+ found: true,
100
+ project: {
101
+ id: 'proj-1',
102
+ name: 'Test Project',
103
+ description: null,
104
+ goal: null,
105
+ status: 'active',
106
+ git_url: null,
107
+ agent_instructions: null,
108
+ tech_stack: null,
109
+ },
110
+ active_tasks: [],
111
+ },
112
+ });
113
+ const ctx = createMockContext();
114
+
115
+ const result = await getProjectContext(
116
+ { project_id: VALID_UUID },
117
+ ctx
118
+ );
119
+
120
+ expect(result.result).toHaveProperty('active_tasks');
121
+ });
122
+
123
+ it('should return error when API call fails', async () => {
124
+ mockApiClient.listProjects.mockResolvedValue({
125
+ ok: false,
126
+ error: 'Database error',
127
+ });
128
+ const ctx = createMockContext();
129
+
130
+ const result = await getProjectContext({}, ctx);
131
+
132
+ expect(result.isError).toBe(true);
133
+ expect(result.result).toMatchObject({ error: 'Database error' });
134
+ });
135
+ });
136
+
137
+ // ============================================================================
138
+ // getGitWorkflow Tests
139
+ // ============================================================================
140
+
141
+ describe('getGitWorkflow', () => {
142
+ beforeEach(() => vi.clearAllMocks());
143
+
144
+ it('should throw error for missing project_id', async () => {
145
+ const ctx = createMockContext();
146
+
147
+ await expect(getGitWorkflow({}, ctx)).rejects.toThrow(ValidationError);
148
+ });
149
+
150
+ it('should throw error for invalid project_id UUID', async () => {
151
+ const ctx = createMockContext();
152
+
153
+ await expect(
154
+ getGitWorkflow({ project_id: 'invalid' }, ctx)
155
+ ).rejects.toThrow(ValidationError);
156
+ });
157
+
158
+ it('should return workflow instructions for github-flow', async () => {
159
+ mockApiClient.getGitWorkflow.mockResolvedValue({
160
+ ok: true,
161
+ data: {
162
+ workflow: 'github-flow',
163
+ main_branch: 'main',
164
+ develop_branch: null,
165
+ auto_branch: true,
166
+ auto_tag: false,
167
+ git_url: 'https://github.com/test',
168
+ instructions: [
169
+ 'Create feature branch from main',
170
+ 'Make changes and commit',
171
+ 'Create PR to main',
172
+ ],
173
+ },
174
+ });
175
+ const ctx = createMockContext();
176
+
177
+ const result = await getGitWorkflow(
178
+ { project_id: VALID_UUID },
179
+ ctx
180
+ );
181
+
182
+ expect(result.result).toMatchObject({
183
+ workflow: 'github-flow',
184
+ main_branch: 'main',
185
+ auto_branch: true,
186
+ });
187
+ expect((result.result as { instructions: string[] }).instructions.length).toBeGreaterThan(0);
188
+ });
189
+
190
+ it('should return workflow instructions for git-flow', async () => {
191
+ mockApiClient.getGitWorkflow.mockResolvedValue({
192
+ ok: true,
193
+ data: {
194
+ workflow: 'git-flow',
195
+ main_branch: 'main',
196
+ develop_branch: 'develop',
197
+ auto_branch: false,
198
+ auto_tag: true,
199
+ git_url: 'https://github.com/test',
200
+ instructions: ['Create feature branch from develop'],
201
+ },
202
+ });
203
+ const ctx = createMockContext();
204
+
205
+ const result = await getGitWorkflow(
206
+ { project_id: VALID_UUID },
207
+ ctx
208
+ );
209
+
210
+ expect(result.result).toMatchObject({
211
+ workflow: 'git-flow',
212
+ main_branch: 'main',
213
+ develop_branch: 'develop',
214
+ });
215
+ });
216
+
217
+ it('should return no workflow instructions when workflow is none', async () => {
218
+ mockApiClient.getGitWorkflow.mockResolvedValue({
219
+ ok: true,
220
+ data: {
221
+ workflow: 'none',
222
+ main_branch: 'main',
223
+ develop_branch: null,
224
+ auto_branch: false,
225
+ auto_tag: false,
226
+ git_url: null,
227
+ instructions: ['No git workflow configured for this project.'],
228
+ },
229
+ });
230
+ const ctx = createMockContext();
231
+
232
+ const result = await getGitWorkflow(
233
+ { project_id: VALID_UUID },
234
+ ctx
235
+ );
236
+
237
+ expect(result.result).toMatchObject({
238
+ workflow: 'none',
239
+ });
240
+ expect((result.result as { instructions: string[] }).instructions[0]).toContain('No git workflow configured');
241
+ });
242
+
243
+ it('should return error when project not found', async () => {
244
+ mockApiClient.getGitWorkflow.mockResolvedValue({
245
+ ok: false,
246
+ error: 'Project not found',
247
+ });
248
+ const ctx = createMockContext();
249
+
250
+ const result = await getGitWorkflow({ project_id: VALID_UUID }, ctx);
251
+
252
+ expect(result.isError).toBe(true);
253
+ expect(result.result).toMatchObject({ error: 'Project not found' });
254
+ });
255
+ });
256
+
257
+ // ============================================================================
258
+ // createProject Tests
259
+ // ============================================================================
260
+
261
+ describe('createProject', () => {
262
+ beforeEach(() => vi.clearAllMocks());
263
+
264
+ it('should create project successfully with minimal args', async () => {
265
+ const mockProject = {
266
+ id: 'new-proj-1',
267
+ name: 'New Project',
268
+ description: null,
269
+ goal: null,
270
+ git_url: null,
271
+ tech_stack: null,
272
+ };
273
+
274
+ mockApiClient.createProject.mockResolvedValue({
275
+ ok: true,
276
+ data: {
277
+ success: true,
278
+ project: mockProject,
279
+ },
280
+ });
281
+ const ctx = createMockContext();
282
+
283
+ const result = await createProject({ name: 'New Project' }, ctx);
284
+
285
+ expect(result.result).toMatchObject({
286
+ success: true,
287
+ project: mockProject,
288
+ });
289
+ expect(mockApiClient.createProject).toHaveBeenCalledWith({
290
+ name: 'New Project',
291
+ description: undefined,
292
+ goal: undefined,
293
+ git_url: undefined,
294
+ tech_stack: undefined,
295
+ });
296
+ });
297
+
298
+ it('should create project with all optional fields', async () => {
299
+ const mockProject = {
300
+ id: 'new-proj-2',
301
+ name: 'Full Project',
302
+ description: 'A test project',
303
+ goal: 'Complete the test',
304
+ git_url: 'https://github.com/test/repo',
305
+ tech_stack: ['TypeScript', 'Svelte'],
306
+ };
307
+
308
+ mockApiClient.createProject.mockResolvedValue({
309
+ ok: true,
310
+ data: {
311
+ success: true,
312
+ project: mockProject,
313
+ },
314
+ });
315
+ const ctx = createMockContext();
316
+
317
+ const result = await createProject(
318
+ {
319
+ name: 'Full Project',
320
+ description: 'A test project',
321
+ goal: 'Complete the test',
322
+ git_url: 'https://github.com/test/repo',
323
+ tech_stack: ['TypeScript', 'Svelte'],
324
+ },
325
+ ctx
326
+ );
327
+
328
+ expect(result.result).toMatchObject({
329
+ success: true,
330
+ project: mockProject,
331
+ });
332
+ });
333
+
334
+ it('should return error when insert fails', async () => {
335
+ mockApiClient.createProject.mockResolvedValue({
336
+ ok: false,
337
+ error: 'Failed to create project',
338
+ });
339
+ const ctx = createMockContext();
340
+
341
+ const result = await createProject({ name: 'Test Project' }, ctx);
342
+
343
+ expect(result.isError).toBe(true);
344
+ expect(result.result).toMatchObject({ error: 'Failed to create project' });
345
+ });
346
+
347
+ it('should throw error when name is missing', async () => {
348
+ const ctx = createMockContext();
349
+
350
+ await expect(
351
+ createProject({}, ctx)
352
+ ).rejects.toThrow(ValidationError);
353
+ });
354
+ });
355
+
356
+ // ============================================================================
357
+ // updateProject Tests
358
+ // ============================================================================
359
+
360
+ describe('updateProject', () => {
361
+ beforeEach(() => vi.clearAllMocks());
362
+
363
+ it('should throw error for missing project_id', async () => {
364
+ const ctx = createMockContext();
365
+
366
+ await expect(updateProject({}, ctx)).rejects.toThrow(ValidationError);
367
+ });
368
+
369
+ it('should throw error for invalid project_id UUID', async () => {
370
+ const ctx = createMockContext();
371
+
372
+ await expect(
373
+ updateProject({ project_id: 'invalid' }, ctx)
374
+ ).rejects.toThrow(ValidationError);
375
+ });
376
+
377
+ it('should throw error for invalid status', async () => {
378
+ const ctx = createMockContext();
379
+
380
+ await expect(
381
+ updateProject({
382
+ project_id: VALID_UUID,
383
+ status: 'invalid_status',
384
+ }, ctx)
385
+ ).rejects.toThrow(ValidationError);
386
+ });
387
+
388
+ it('should update project successfully', async () => {
389
+ mockApiClient.updateProject.mockResolvedValue({
390
+ ok: true,
391
+ data: {
392
+ success: true,
393
+ project_id: VALID_UUID,
394
+ },
395
+ });
396
+ const ctx = createMockContext();
397
+
398
+ const result = await updateProject(
399
+ {
400
+ project_id: VALID_UUID,
401
+ name: 'Updated Name',
402
+ status: 'active',
403
+ },
404
+ ctx
405
+ );
406
+
407
+ expect(result.result).toMatchObject({
408
+ success: true,
409
+ project_id: VALID_UUID,
410
+ });
411
+ expect(mockApiClient.updateProject).toHaveBeenCalledWith(
412
+ VALID_UUID,
413
+ {
414
+ name: 'Updated Name',
415
+ status: 'active',
416
+ }
417
+ );
418
+ });
419
+
420
+ it('should update git workflow settings', async () => {
421
+ mockApiClient.updateProject.mockResolvedValue({
422
+ ok: true,
423
+ data: {
424
+ success: true,
425
+ project_id: VALID_UUID,
426
+ },
427
+ });
428
+ const ctx = createMockContext();
429
+
430
+ const result = await updateProject(
431
+ {
432
+ project_id: VALID_UUID,
433
+ git_workflow: 'github-flow',
434
+ git_main_branch: 'main',
435
+ git_auto_branch: true,
436
+ },
437
+ ctx
438
+ );
439
+
440
+ expect(result.result).toMatchObject({
441
+ success: true,
442
+ });
443
+ expect(mockApiClient.updateProject).toHaveBeenCalledWith(
444
+ VALID_UUID,
445
+ {
446
+ git_workflow: 'github-flow',
447
+ git_main_branch: 'main',
448
+ git_auto_branch: true,
449
+ }
450
+ );
451
+ });
452
+
453
+ it('should return error when update fails', async () => {
454
+ mockApiClient.updateProject.mockResolvedValue({
455
+ ok: false,
456
+ error: 'Failed to update project',
457
+ });
458
+ const ctx = createMockContext();
459
+
460
+ const result = await updateProject({
461
+ project_id: VALID_UUID,
462
+ name: 'Test',
463
+ }, ctx);
464
+
465
+ expect(result.isError).toBe(true);
466
+ expect(result.result).toMatchObject({ error: 'Failed to update project' });
467
+ });
468
+ });
469
+
470
+ // ============================================================================
471
+ // updateProjectReadme Tests
472
+ // ============================================================================
473
+
474
+ describe('updateProjectReadme', () => {
475
+ beforeEach(() => vi.clearAllMocks());
476
+
477
+ it('should throw error for missing project_id', async () => {
478
+ const ctx = createMockContext();
479
+
480
+ await expect(
481
+ updateProjectReadme({ readme_content: '# README' }, ctx)
482
+ ).rejects.toThrow(ValidationError);
483
+ });
484
+
485
+ it('should throw error for missing readme_content', async () => {
486
+ const ctx = createMockContext();
487
+
488
+ await expect(
489
+ updateProjectReadme({ project_id: VALID_UUID }, ctx)
490
+ ).rejects.toThrow(ValidationError);
491
+ });
492
+
493
+ it('should throw error for invalid project_id UUID', async () => {
494
+ const ctx = createMockContext();
495
+
496
+ await expect(
497
+ updateProjectReadme({
498
+ project_id: 'invalid',
499
+ readme_content: '# README',
500
+ }, ctx)
501
+ ).rejects.toThrow(ValidationError);
502
+ });
503
+
504
+ it('should update readme successfully', async () => {
505
+ mockApiClient.updateProjectReadme.mockResolvedValue({
506
+ ok: true,
507
+ data: {
508
+ success: true,
509
+ project_id: VALID_UUID,
510
+ },
511
+ });
512
+ const ctx = createMockContext();
513
+
514
+ const result = await updateProjectReadme(
515
+ {
516
+ project_id: VALID_UUID,
517
+ readme_content: '# My Project\n\nDescription here.',
518
+ },
519
+ ctx
520
+ );
521
+
522
+ expect(result.result).toMatchObject({
523
+ success: true,
524
+ });
525
+ expect(mockApiClient.updateProjectReadme).toHaveBeenCalledWith(
526
+ VALID_UUID,
527
+ '# My Project\n\nDescription here.'
528
+ );
529
+ });
530
+
531
+ it('should return error when update fails', async () => {
532
+ mockApiClient.updateProjectReadme.mockResolvedValue({
533
+ ok: false,
534
+ error: 'Failed to update README',
535
+ });
536
+ const ctx = createMockContext();
537
+
538
+ const result = await updateProjectReadme({
539
+ project_id: VALID_UUID,
540
+ readme_content: '# README',
541
+ }, ctx);
542
+
543
+ expect(result.isError).toBe(true);
544
+ expect(result.result).toMatchObject({ error: 'Failed to update README' });
545
+ });
546
+ });