@vibescope/mcp-server 0.0.1 → 0.2.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.
- package/README.md +113 -98
- package/dist/api-client.d.ts +1169 -0
- package/dist/api-client.js +713 -0
- package/dist/cli.d.ts +1 -6
- package/dist/cli.js +39 -240
- package/dist/config/tool-categories.d.ts +31 -0
- package/dist/config/tool-categories.js +253 -0
- package/dist/handlers/blockers.js +57 -58
- package/dist/handlers/bodies-of-work.d.ts +2 -0
- package/dist/handlers/bodies-of-work.js +108 -477
- package/dist/handlers/cost.d.ts +1 -0
- package/dist/handlers/cost.js +35 -113
- package/dist/handlers/decisions.d.ts +2 -0
- package/dist/handlers/decisions.js +28 -27
- package/dist/handlers/deployment.js +113 -828
- package/dist/handlers/discovery.d.ts +3 -0
- package/dist/handlers/discovery.js +26 -627
- package/dist/handlers/fallback.d.ts +2 -0
- package/dist/handlers/fallback.js +56 -142
- package/dist/handlers/findings.d.ts +8 -1
- package/dist/handlers/findings.js +65 -68
- package/dist/handlers/git-issues.d.ts +9 -13
- package/dist/handlers/git-issues.js +80 -225
- package/dist/handlers/ideas.d.ts +3 -0
- package/dist/handlers/ideas.js +53 -134
- package/dist/handlers/index.d.ts +2 -0
- package/dist/handlers/index.js +6 -0
- package/dist/handlers/milestones.d.ts +2 -0
- package/dist/handlers/milestones.js +51 -98
- package/dist/handlers/organizations.js +79 -275
- package/dist/handlers/progress.d.ts +2 -0
- package/dist/handlers/progress.js +25 -123
- package/dist/handlers/project.js +42 -221
- package/dist/handlers/requests.d.ts +2 -0
- package/dist/handlers/requests.js +23 -83
- package/dist/handlers/session.js +119 -590
- package/dist/handlers/sprints.d.ts +32 -0
- package/dist/handlers/sprints.js +275 -0
- package/dist/handlers/tasks.d.ts +7 -10
- package/dist/handlers/tasks.js +245 -894
- package/dist/handlers/tool-docs.d.ts +9 -0
- package/dist/handlers/tool-docs.js +904 -0
- package/dist/handlers/types.d.ts +11 -3
- package/dist/handlers/validation.d.ts +1 -1
- package/dist/handlers/validation.js +38 -153
- package/dist/index.js +493 -162
- package/dist/knowledge.js +106 -9
- package/dist/tools.js +34 -4
- package/dist/validators.d.ts +21 -0
- package/dist/validators.js +91 -0
- package/package.json +2 -3
- package/src/api-client.ts +1822 -0
- package/src/cli.test.ts +128 -302
- package/src/cli.ts +41 -285
- package/src/handlers/__test-setup__.ts +215 -0
- package/src/handlers/__test-utils__.ts +4 -134
- package/src/handlers/blockers.test.ts +114 -124
- package/src/handlers/blockers.ts +68 -70
- package/src/handlers/bodies-of-work.test.ts +236 -831
- package/src/handlers/bodies-of-work.ts +210 -525
- package/src/handlers/cost.test.ts +149 -113
- package/src/handlers/cost.ts +44 -132
- package/src/handlers/decisions.test.ts +111 -209
- package/src/handlers/decisions.ts +35 -27
- package/src/handlers/deployment.test.ts +193 -239
- package/src/handlers/deployment.ts +143 -896
- package/src/handlers/discovery.test.ts +20 -67
- package/src/handlers/discovery.ts +29 -714
- package/src/handlers/fallback.test.ts +206 -361
- package/src/handlers/fallback.ts +81 -156
- package/src/handlers/findings.test.ts +229 -320
- package/src/handlers/findings.ts +76 -64
- package/src/handlers/git-issues.test.ts +623 -0
- package/src/handlers/git-issues.ts +174 -0
- package/src/handlers/ideas.test.ts +229 -343
- package/src/handlers/ideas.ts +69 -143
- package/src/handlers/index.ts +6 -0
- package/src/handlers/milestones.test.ts +167 -281
- package/src/handlers/milestones.ts +54 -93
- package/src/handlers/organizations.test.ts +275 -467
- package/src/handlers/organizations.ts +84 -294
- package/src/handlers/progress.test.ts +112 -218
- package/src/handlers/progress.ts +29 -142
- package/src/handlers/project.test.ts +203 -226
- package/src/handlers/project.ts +48 -238
- package/src/handlers/requests.test.ts +74 -342
- package/src/handlers/requests.ts +25 -83
- package/src/handlers/session.test.ts +276 -206
- package/src/handlers/session.ts +136 -662
- package/src/handlers/sprints.test.ts +711 -0
- package/src/handlers/sprints.ts +510 -0
- package/src/handlers/tasks.test.ts +669 -353
- package/src/handlers/tasks.ts +263 -1015
- package/src/handlers/tool-docs.ts +1024 -0
- package/src/handlers/types.ts +12 -4
- package/src/handlers/validation.test.ts +237 -568
- package/src/handlers/validation.ts +43 -167
- package/src/index.ts +493 -186
- package/src/tools.ts +2532 -0
- package/src/validators.test.ts +223 -223
- package/src/validators.ts +127 -0
- package/tsconfig.json +1 -1
- package/vitest.config.ts +14 -13
- package/dist/cli.test.d.ts +0 -1
- package/dist/cli.test.js +0 -367
- package/dist/handlers/__test-utils__.d.ts +0 -72
- package/dist/handlers/__test-utils__.js +0 -176
- package/dist/handlers/checkouts.d.ts +0 -37
- package/dist/handlers/checkouts.js +0 -377
- package/dist/handlers/knowledge-query.d.ts +0 -22
- package/dist/handlers/knowledge-query.js +0 -253
- package/dist/handlers/knowledge.d.ts +0 -12
- package/dist/handlers/knowledge.js +0 -108
- package/dist/handlers/roles.d.ts +0 -30
- package/dist/handlers/roles.js +0 -281
- package/dist/handlers/tasks.test.d.ts +0 -1
- package/dist/handlers/tasks.test.js +0 -431
- package/dist/utils.test.d.ts +0 -1
- package/dist/utils.test.js +0 -532
- package/dist/validators.test.d.ts +0 -1
- package/dist/validators.test.js +0 -176
- package/src/knowledge.ts +0 -132
- package/src/tmpclaude-0078-cwd +0 -1
- package/src/tmpclaude-0ee1-cwd +0 -1
- package/src/tmpclaude-2dd5-cwd +0 -1
- package/src/tmpclaude-344c-cwd +0 -1
- package/src/tmpclaude-3860-cwd +0 -1
- package/src/tmpclaude-4b63-cwd +0 -1
- package/src/tmpclaude-5c73-cwd +0 -1
- package/src/tmpclaude-5ee3-cwd +0 -1
- package/src/tmpclaude-6795-cwd +0 -1
- package/src/tmpclaude-709e-cwd +0 -1
- package/src/tmpclaude-9839-cwd +0 -1
- package/src/tmpclaude-d829-cwd +0 -1
- package/src/tmpclaude-e072-cwd +0 -1
- package/src/tmpclaude-f6ee-cwd +0 -1
- package/tmpclaude-0439-cwd +0 -1
- package/tmpclaude-132f-cwd +0 -1
- package/tmpclaude-15bb-cwd +0 -1
- package/tmpclaude-165a-cwd +0 -1
- package/tmpclaude-1ba9-cwd +0 -1
- package/tmpclaude-21a3-cwd +0 -1
- package/tmpclaude-2a38-cwd +0 -1
- package/tmpclaude-2adf-cwd +0 -1
- package/tmpclaude-2f56-cwd +0 -1
- package/tmpclaude-3626-cwd +0 -1
- package/tmpclaude-3727-cwd +0 -1
- package/tmpclaude-40bc-cwd +0 -1
- package/tmpclaude-436f-cwd +0 -1
- package/tmpclaude-4783-cwd +0 -1
- package/tmpclaude-4b6d-cwd +0 -1
- package/tmpclaude-4ba4-cwd +0 -1
- package/tmpclaude-51e6-cwd +0 -1
- package/tmpclaude-5ecf-cwd +0 -1
- package/tmpclaude-6f97-cwd +0 -1
- package/tmpclaude-7fb2-cwd +0 -1
- package/tmpclaude-825c-cwd +0 -1
- package/tmpclaude-8baf-cwd +0 -1
- package/tmpclaude-8d9f-cwd +0 -1
- package/tmpclaude-975c-cwd +0 -1
- package/tmpclaude-9983-cwd +0 -1
- package/tmpclaude-a045-cwd +0 -1
- package/tmpclaude-ac4a-cwd +0 -1
- package/tmpclaude-b593-cwd +0 -1
- package/tmpclaude-b891-cwd +0 -1
- package/tmpclaude-c032-cwd +0 -1
- package/tmpclaude-cf43-cwd +0 -1
- package/tmpclaude-d040-cwd +0 -1
- package/tmpclaude-dcdd-cwd +0 -1
- package/tmpclaude-dcee-cwd +0 -1
- package/tmpclaude-e16b-cwd +0 -1
- package/tmpclaude-ecd2-cwd +0 -1
- package/tmpclaude-f48d-cwd +0 -1
|
@@ -1,85 +1,24 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
3
|
-
import type { HandlerContext, TokenUsage } from './types.js';
|
|
4
2
|
import { logProgress, getActivityFeed } from './progress.js';
|
|
5
3
|
import { ValidationError } from '../validators.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
// Test Utilities
|
|
9
|
-
// ============================================================================
|
|
10
|
-
|
|
11
|
-
function createMockSupabase(overrides: {
|
|
12
|
-
selectResult?: { data: unknown; error: unknown };
|
|
13
|
-
insertResult?: { data: unknown; error: unknown };
|
|
14
|
-
} = {}) {
|
|
15
|
-
const defaultResult = { data: null, error: null };
|
|
16
|
-
let currentOperation = 'select';
|
|
17
|
-
|
|
18
|
-
const mock = {
|
|
19
|
-
from: vi.fn().mockReturnThis(),
|
|
20
|
-
select: vi.fn(() => {
|
|
21
|
-
currentOperation = 'select';
|
|
22
|
-
return mock;
|
|
23
|
-
}),
|
|
24
|
-
insert: vi.fn(() => {
|
|
25
|
-
currentOperation = 'insert';
|
|
26
|
-
return mock;
|
|
27
|
-
}),
|
|
28
|
-
eq: vi.fn().mockReturnThis(),
|
|
29
|
-
gt: vi.fn().mockReturnThis(),
|
|
30
|
-
order: vi.fn().mockReturnThis(),
|
|
31
|
-
limit: vi.fn().mockReturnThis(),
|
|
32
|
-
then: vi.fn((resolve: (value: unknown) => void) => {
|
|
33
|
-
if (currentOperation === 'insert') {
|
|
34
|
-
return Promise.resolve(overrides.insertResult ?? defaultResult).then(resolve);
|
|
35
|
-
}
|
|
36
|
-
if (currentOperation === 'select') {
|
|
37
|
-
return Promise.resolve(overrides.selectResult ?? defaultResult).then(resolve);
|
|
38
|
-
}
|
|
39
|
-
return Promise.resolve(defaultResult).then(resolve);
|
|
40
|
-
}),
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
return mock as unknown as SupabaseClient;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function createMockContext(
|
|
47
|
-
supabase: SupabaseClient,
|
|
48
|
-
options: { sessionId?: string | null } = {}
|
|
49
|
-
): HandlerContext {
|
|
50
|
-
const defaultTokenUsage: TokenUsage = {
|
|
51
|
-
callCount: 5,
|
|
52
|
-
totalTokens: 2500,
|
|
53
|
-
byTool: {},
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const sessionId = 'sessionId' in options ? options.sessionId : 'session-123';
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
supabase,
|
|
60
|
-
auth: { userId: 'user-123', apiKeyId: 'api-key-123' },
|
|
61
|
-
session: {
|
|
62
|
-
instanceId: 'instance-abc',
|
|
63
|
-
currentSessionId: sessionId,
|
|
64
|
-
currentPersona: 'Wave',
|
|
65
|
-
tokenUsage: defaultTokenUsage,
|
|
66
|
-
},
|
|
67
|
-
updateSession: vi.fn(),
|
|
68
|
-
};
|
|
69
|
-
}
|
|
4
|
+
import { createMockContext } from './__test-utils__.js';
|
|
5
|
+
import { mockApiClient } from './__test-setup__.js';
|
|
70
6
|
|
|
71
7
|
// ============================================================================
|
|
72
8
|
// logProgress Tests
|
|
73
9
|
// ============================================================================
|
|
74
10
|
|
|
75
11
|
describe('logProgress', () => {
|
|
76
|
-
beforeEach(() =>
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.clearAllMocks();
|
|
14
|
+
});
|
|
77
15
|
|
|
78
16
|
it('should log progress successfully', async () => {
|
|
79
|
-
|
|
80
|
-
|
|
17
|
+
mockApiClient.logProgress.mockResolvedValue({
|
|
18
|
+
ok: true,
|
|
19
|
+
data: { progress_id: 'prog-123' },
|
|
81
20
|
});
|
|
82
|
-
const ctx = createMockContext(
|
|
21
|
+
const ctx = createMockContext();
|
|
83
22
|
|
|
84
23
|
const result = await logProgress(
|
|
85
24
|
{
|
|
@@ -91,14 +30,16 @@ describe('logProgress', () => {
|
|
|
91
30
|
|
|
92
31
|
expect(result.result).toMatchObject({
|
|
93
32
|
success: true,
|
|
33
|
+
progress_id: 'prog-123',
|
|
94
34
|
});
|
|
95
35
|
});
|
|
96
36
|
|
|
97
37
|
it('should log progress with task_id and details', async () => {
|
|
98
|
-
|
|
99
|
-
|
|
38
|
+
mockApiClient.logProgress.mockResolvedValue({
|
|
39
|
+
ok: true,
|
|
40
|
+
data: { progress_id: 'prog-456' },
|
|
100
41
|
});
|
|
101
|
-
const ctx = createMockContext(
|
|
42
|
+
const ctx = createMockContext();
|
|
102
43
|
|
|
103
44
|
await logProgress(
|
|
104
45
|
{
|
|
@@ -110,23 +51,23 @@ describe('logProgress', () => {
|
|
|
110
51
|
ctx
|
|
111
52
|
);
|
|
112
53
|
|
|
113
|
-
expect(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
task_id: '456e4567-e89b-12d3-a456-426614174000',
|
|
54
|
+
expect(mockApiClient.logProgress).toHaveBeenCalledWith(
|
|
55
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
56
|
+
{
|
|
117
57
|
summary: 'Working on feature',
|
|
118
58
|
details: 'Added the main component and started styling',
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
59
|
+
task_id: '456e4567-e89b-12d3-a456-426614174000',
|
|
60
|
+
session_id: 'session-123',
|
|
61
|
+
}
|
|
122
62
|
);
|
|
123
63
|
});
|
|
124
64
|
|
|
125
|
-
it('should include session_id in
|
|
126
|
-
|
|
127
|
-
|
|
65
|
+
it('should include session_id in request', async () => {
|
|
66
|
+
mockApiClient.logProgress.mockResolvedValue({
|
|
67
|
+
ok: true,
|
|
68
|
+
data: { progress_id: 'prog-789' },
|
|
128
69
|
});
|
|
129
|
-
const ctx = createMockContext(
|
|
70
|
+
const ctx = createMockContext({ sessionId: 'my-session' });
|
|
130
71
|
|
|
131
72
|
await logProgress(
|
|
132
73
|
{
|
|
@@ -136,19 +77,20 @@ describe('logProgress', () => {
|
|
|
136
77
|
ctx
|
|
137
78
|
);
|
|
138
79
|
|
|
139
|
-
expect(
|
|
80
|
+
expect(mockApiClient.logProgress).toHaveBeenCalledWith(
|
|
81
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
140
82
|
expect.objectContaining({
|
|
141
|
-
|
|
142
|
-
created_by_session_id: 'my-session',
|
|
83
|
+
session_id: 'my-session',
|
|
143
84
|
})
|
|
144
85
|
);
|
|
145
86
|
});
|
|
146
87
|
|
|
147
|
-
it('should
|
|
148
|
-
|
|
149
|
-
|
|
88
|
+
it('should call API client logProgress', async () => {
|
|
89
|
+
mockApiClient.logProgress.mockResolvedValue({
|
|
90
|
+
ok: true,
|
|
91
|
+
data: { progress_id: 'prog-abc' },
|
|
150
92
|
});
|
|
151
|
-
const ctx = createMockContext(
|
|
93
|
+
const ctx = createMockContext();
|
|
152
94
|
|
|
153
95
|
await logProgress(
|
|
154
96
|
{
|
|
@@ -158,14 +100,15 @@ describe('logProgress', () => {
|
|
|
158
100
|
ctx
|
|
159
101
|
);
|
|
160
102
|
|
|
161
|
-
expect(
|
|
103
|
+
expect(mockApiClient.logProgress).toHaveBeenCalled();
|
|
162
104
|
});
|
|
163
105
|
|
|
164
|
-
it('should throw error when
|
|
165
|
-
|
|
166
|
-
|
|
106
|
+
it('should throw error when API call fails', async () => {
|
|
107
|
+
mockApiClient.logProgress.mockResolvedValue({
|
|
108
|
+
ok: false,
|
|
109
|
+
error: 'Insert failed',
|
|
167
110
|
});
|
|
168
|
-
const ctx = createMockContext(
|
|
111
|
+
const ctx = createMockContext();
|
|
169
112
|
|
|
170
113
|
await expect(
|
|
171
114
|
logProgress({
|
|
@@ -174,6 +117,30 @@ describe('logProgress', () => {
|
|
|
174
117
|
}, ctx)
|
|
175
118
|
).rejects.toThrow('Failed to log progress: Insert failed');
|
|
176
119
|
});
|
|
120
|
+
|
|
121
|
+
it('should throw error for missing project_id', async () => {
|
|
122
|
+
const ctx = createMockContext();
|
|
123
|
+
|
|
124
|
+
await expect(
|
|
125
|
+
logProgress({ summary: 'Progress' }, ctx)
|
|
126
|
+
).rejects.toThrow(ValidationError);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should throw error for invalid project_id UUID', async () => {
|
|
130
|
+
const ctx = createMockContext();
|
|
131
|
+
|
|
132
|
+
await expect(
|
|
133
|
+
logProgress({ project_id: 'invalid', summary: 'Progress' }, ctx)
|
|
134
|
+
).rejects.toThrow(ValidationError);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should throw error for missing summary', async () => {
|
|
138
|
+
const ctx = createMockContext();
|
|
139
|
+
|
|
140
|
+
await expect(
|
|
141
|
+
logProgress({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
142
|
+
).rejects.toThrow(ValidationError);
|
|
143
|
+
});
|
|
177
144
|
});
|
|
178
145
|
|
|
179
146
|
// ============================================================================
|
|
@@ -181,18 +148,18 @@ describe('logProgress', () => {
|
|
|
181
148
|
// ============================================================================
|
|
182
149
|
|
|
183
150
|
describe('getActivityFeed', () => {
|
|
184
|
-
beforeEach(() =>
|
|
151
|
+
beforeEach(() => {
|
|
152
|
+
vi.clearAllMocks();
|
|
153
|
+
});
|
|
185
154
|
|
|
186
155
|
it('should throw error for missing project_id', async () => {
|
|
187
|
-
const
|
|
188
|
-
const ctx = createMockContext(supabase);
|
|
156
|
+
const ctx = createMockContext();
|
|
189
157
|
|
|
190
158
|
await expect(getActivityFeed({}, ctx)).rejects.toThrow(ValidationError);
|
|
191
159
|
});
|
|
192
160
|
|
|
193
161
|
it('should throw error for invalid project_id UUID', async () => {
|
|
194
|
-
const
|
|
195
|
-
const ctx = createMockContext(supabase);
|
|
162
|
+
const ctx = createMockContext();
|
|
196
163
|
|
|
197
164
|
await expect(
|
|
198
165
|
getActivityFeed({ project_id: 'invalid' }, ctx)
|
|
@@ -200,10 +167,11 @@ describe('getActivityFeed', () => {
|
|
|
200
167
|
});
|
|
201
168
|
|
|
202
169
|
it('should return empty activities when no data', async () => {
|
|
203
|
-
|
|
204
|
-
|
|
170
|
+
mockApiClient.getActivityFeed.mockResolvedValue({
|
|
171
|
+
ok: true,
|
|
172
|
+
data: { activities: [] },
|
|
205
173
|
});
|
|
206
|
-
const ctx = createMockContext(
|
|
174
|
+
const ctx = createMockContext();
|
|
207
175
|
|
|
208
176
|
const result = await getActivityFeed(
|
|
209
177
|
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
@@ -215,50 +183,33 @@ describe('getActivityFeed', () => {
|
|
|
215
183
|
});
|
|
216
184
|
});
|
|
217
185
|
|
|
218
|
-
it('should
|
|
219
|
-
const
|
|
220
|
-
|
|
186
|
+
it('should return activities from API', async () => {
|
|
187
|
+
const mockActivities = [
|
|
188
|
+
{ id: 'a1', type: 'task', created_at: '2025-01-14T10:00:00Z' },
|
|
189
|
+
{ id: 'a2', type: 'progress', created_at: '2025-01-14T11:00:00Z' },
|
|
190
|
+
];
|
|
191
|
+
mockApiClient.getActivityFeed.mockResolvedValue({
|
|
192
|
+
ok: true,
|
|
193
|
+
data: { activities: mockActivities },
|
|
221
194
|
});
|
|
222
|
-
const ctx = createMockContext(
|
|
195
|
+
const ctx = createMockContext();
|
|
223
196
|
|
|
224
|
-
await getActivityFeed(
|
|
197
|
+
const result = await getActivityFeed(
|
|
225
198
|
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
226
199
|
ctx
|
|
227
200
|
);
|
|
228
201
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
expect(supabase.from).toHaveBeenCalledWith('progress_logs');
|
|
232
|
-
expect(supabase.from).toHaveBeenCalledWith('blockers');
|
|
233
|
-
expect(supabase.from).toHaveBeenCalledWith('decisions');
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it('should filter by specific types', async () => {
|
|
237
|
-
const supabase = createMockSupabase({
|
|
238
|
-
selectResult: { data: [], error: null },
|
|
202
|
+
expect(result.result).toMatchObject({
|
|
203
|
+
activities: mockActivities,
|
|
239
204
|
});
|
|
240
|
-
const ctx = createMockContext(supabase);
|
|
241
|
-
|
|
242
|
-
await getActivityFeed(
|
|
243
|
-
{
|
|
244
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
245
|
-
types: ['task', 'blocker'],
|
|
246
|
-
},
|
|
247
|
-
ctx
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
// Should only query tasks and blockers
|
|
251
|
-
expect(supabase.from).toHaveBeenCalledWith('tasks');
|
|
252
|
-
expect(supabase.from).toHaveBeenCalledWith('blockers');
|
|
253
|
-
expect(supabase.from).not.toHaveBeenCalledWith('progress_logs');
|
|
254
|
-
expect(supabase.from).not.toHaveBeenCalledWith('decisions');
|
|
255
205
|
});
|
|
256
206
|
|
|
257
|
-
it('should
|
|
258
|
-
|
|
259
|
-
|
|
207
|
+
it('should pass limit parameter to API', async () => {
|
|
208
|
+
mockApiClient.getActivityFeed.mockResolvedValue({
|
|
209
|
+
ok: true,
|
|
210
|
+
data: { activities: [] },
|
|
260
211
|
});
|
|
261
|
-
const ctx = createMockContext(
|
|
212
|
+
const ctx = createMockContext();
|
|
262
213
|
|
|
263
214
|
await getActivityFeed(
|
|
264
215
|
{
|
|
@@ -268,16 +219,20 @@ describe('getActivityFeed', () => {
|
|
|
268
219
|
ctx
|
|
269
220
|
);
|
|
270
221
|
|
|
271
|
-
expect(
|
|
222
|
+
expect(mockApiClient.getActivityFeed).toHaveBeenCalledWith(
|
|
223
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
224
|
+
expect.objectContaining({ limit: 10 })
|
|
225
|
+
);
|
|
272
226
|
});
|
|
273
227
|
|
|
274
228
|
it('should cap limit at 200', async () => {
|
|
275
|
-
|
|
276
|
-
|
|
229
|
+
mockApiClient.getActivityFeed.mockResolvedValue({
|
|
230
|
+
ok: true,
|
|
231
|
+
data: { activities: [] },
|
|
277
232
|
});
|
|
278
|
-
const ctx = createMockContext(
|
|
233
|
+
const ctx = createMockContext();
|
|
279
234
|
|
|
280
|
-
|
|
235
|
+
await getActivityFeed(
|
|
281
236
|
{
|
|
282
237
|
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
283
238
|
limit: 500,
|
|
@@ -285,85 +240,24 @@ describe('getActivityFeed', () => {
|
|
|
285
240
|
ctx
|
|
286
241
|
);
|
|
287
242
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
it('should sort activities by created_at descending', async () => {
|
|
293
|
-
const mockTasks = [
|
|
294
|
-
{ id: 't1', title: 'Task 1', status: 'pending', created_by: 'agent', created_at: '2025-01-14T10:00:00Z' },
|
|
295
|
-
];
|
|
296
|
-
const mockProgress = [
|
|
297
|
-
{ id: 'p1', summary: 'Progress 1', created_by: 'agent', created_at: '2025-01-14T11:00:00Z' },
|
|
298
|
-
];
|
|
299
|
-
|
|
300
|
-
let callCount = 0;
|
|
301
|
-
const supabase = {
|
|
302
|
-
from: vi.fn().mockReturnThis(),
|
|
303
|
-
select: vi.fn().mockReturnThis(),
|
|
304
|
-
eq: vi.fn().mockReturnThis(),
|
|
305
|
-
gt: vi.fn().mockReturnThis(),
|
|
306
|
-
order: vi.fn().mockReturnThis(),
|
|
307
|
-
limit: vi.fn().mockReturnThis(),
|
|
308
|
-
then: vi.fn((resolve: (val: unknown) => void) => {
|
|
309
|
-
callCount++;
|
|
310
|
-
if (callCount === 1) {
|
|
311
|
-
return Promise.resolve({ data: mockTasks, error: null }).then(resolve);
|
|
312
|
-
}
|
|
313
|
-
if (callCount === 2) {
|
|
314
|
-
return Promise.resolve({ data: mockProgress, error: null }).then(resolve);
|
|
315
|
-
}
|
|
316
|
-
return Promise.resolve({ data: [], error: null }).then(resolve);
|
|
317
|
-
}),
|
|
318
|
-
} as unknown as SupabaseClient;
|
|
319
|
-
|
|
320
|
-
const ctx = createMockContext(supabase);
|
|
321
|
-
|
|
322
|
-
const result = await getActivityFeed(
|
|
323
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
324
|
-
ctx
|
|
243
|
+
expect(mockApiClient.getActivityFeed).toHaveBeenCalledWith(
|
|
244
|
+
'123e4567-e89b-12d3-a456-426614174000',
|
|
245
|
+
expect.objectContaining({ limit: 200 })
|
|
325
246
|
);
|
|
326
|
-
|
|
327
|
-
const activities = (result.result as { activities: { created_at: string }[] }).activities;
|
|
328
|
-
// Should be sorted by date descending (most recent first)
|
|
329
|
-
for (let i = 1; i < activities.length; i++) {
|
|
330
|
-
const prevDate = new Date(activities[i - 1].created_at).getTime();
|
|
331
|
-
const currDate = new Date(activities[i].created_at).getTime();
|
|
332
|
-
expect(prevDate).toBeGreaterThanOrEqual(currDate);
|
|
333
|
-
}
|
|
334
247
|
});
|
|
335
248
|
|
|
336
|
-
it('should
|
|
337
|
-
|
|
338
|
-
|
|
249
|
+
it('should throw error when API call fails', async () => {
|
|
250
|
+
mockApiClient.getActivityFeed.mockResolvedValue({
|
|
251
|
+
ok: false,
|
|
252
|
+
error: 'Query failed',
|
|
339
253
|
});
|
|
340
|
-
const ctx = createMockContext(
|
|
254
|
+
const ctx = createMockContext();
|
|
341
255
|
|
|
342
|
-
await
|
|
343
|
-
|
|
344
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
);
|
|
349
|
-
|
|
350
|
-
expect(supabase.eq).toHaveBeenCalledWith('created_by', 'agent');
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
it('should filter by since parameter', async () => {
|
|
354
|
-
const supabase = createMockSupabase({
|
|
355
|
-
selectResult: { data: [], error: null },
|
|
356
|
-
});
|
|
357
|
-
const ctx = createMockContext(supabase);
|
|
358
|
-
|
|
359
|
-
await getActivityFeed(
|
|
360
|
-
{
|
|
361
|
-
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
362
|
-
since: '2025-01-14T00:00:00Z',
|
|
363
|
-
},
|
|
364
|
-
ctx
|
|
365
|
-
);
|
|
366
|
-
|
|
367
|
-
expect(supabase.gt).toHaveBeenCalledWith('created_at', '2025-01-14T00:00:00Z');
|
|
256
|
+
await expect(
|
|
257
|
+
getActivityFeed(
|
|
258
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
259
|
+
ctx
|
|
260
|
+
)
|
|
261
|
+
).rejects.toThrow('Failed to fetch activity feed: Query failed');
|
|
368
262
|
});
|
|
369
263
|
});
|
package/src/handlers/progress.ts
CHANGED
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
* Handles progress logging and activity feed:
|
|
5
5
|
* - log_progress
|
|
6
6
|
* - get_activity_feed
|
|
7
|
+
*
|
|
8
|
+
* MIGRATED: Uses Vibescope API client instead of direct Supabase
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
import type { Handler, HandlerRegistry } from './types.js';
|
|
10
12
|
import { validateRequired, validateUUID } from '../validators.js';
|
|
13
|
+
import { getApiClient } from '../api-client.js';
|
|
11
14
|
|
|
12
15
|
export const logProgress: Handler = async (args, ctx) => {
|
|
13
16
|
const { project_id, task_id, summary, details } = args as {
|
|
@@ -17,166 +20,50 @@ export const logProgress: Handler = async (args, ctx) => {
|
|
|
17
20
|
details?: string;
|
|
18
21
|
};
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
validateRequired(project_id, 'project_id');
|
|
24
|
+
validateUUID(project_id, 'project_id');
|
|
25
|
+
validateRequired(summary, 'summary');
|
|
26
|
+
|
|
27
|
+
const { session } = ctx;
|
|
28
|
+
const apiClient = getApiClient();
|
|
21
29
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
details: details || null,
|
|
29
|
-
created_by: 'agent',
|
|
30
|
-
created_by_session_id: session.currentSessionId,
|
|
31
|
-
});
|
|
30
|
+
const response = await apiClient.logProgress(project_id, {
|
|
31
|
+
summary,
|
|
32
|
+
details,
|
|
33
|
+
task_id,
|
|
34
|
+
session_id: session.currentSessionId || undefined
|
|
35
|
+
});
|
|
32
36
|
|
|
33
|
-
if (
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
throw new Error(`Failed to log progress: ${response.error}`);
|
|
39
|
+
}
|
|
34
40
|
|
|
35
|
-
return { result: { success: true } };
|
|
41
|
+
return { result: { success: true, progress_id: response.data?.progress_id } };
|
|
36
42
|
};
|
|
37
43
|
|
|
38
44
|
export const getActivityFeed: Handler = async (args, ctx) => {
|
|
39
|
-
const { project_id,
|
|
45
|
+
const { project_id, limit = 50, since } = args as {
|
|
40
46
|
project_id: string;
|
|
41
|
-
types?: ('task' | 'progress' | 'blocker' | 'decision')[];
|
|
42
|
-
created_by?: 'agent' | 'user';
|
|
43
|
-
since?: string;
|
|
44
47
|
limit?: number;
|
|
48
|
+
since?: string;
|
|
45
49
|
};
|
|
46
50
|
|
|
47
51
|
validateRequired(project_id, 'project_id');
|
|
48
52
|
validateUUID(project_id, 'project_id');
|
|
49
53
|
|
|
50
|
-
const
|
|
54
|
+
const apiClient = getApiClient();
|
|
51
55
|
const effectiveLimit = Math.min(limit, 200);
|
|
52
|
-
const includeTypes = types || ['task', 'progress', 'blocker', 'decision'];
|
|
53
|
-
// Fetch slightly more per type to account for interleaving, but not full limit
|
|
54
|
-
const perTypeLimit = Math.min(Math.ceil(effectiveLimit / includeTypes.length) * 2, effectiveLimit);
|
|
55
|
-
|
|
56
|
-
interface ActivityItem {
|
|
57
|
-
type: string;
|
|
58
|
-
id: string;
|
|
59
|
-
title?: string;
|
|
60
|
-
summary?: string;
|
|
61
|
-
description?: string;
|
|
62
|
-
status?: string;
|
|
63
|
-
created_by: string;
|
|
64
|
-
created_at: string;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const activities: ActivityItem[] = [];
|
|
68
|
-
|
|
69
|
-
// Fetch tasks if included
|
|
70
|
-
if (includeTypes.includes('task')) {
|
|
71
|
-
let query = supabase
|
|
72
|
-
.from('tasks')
|
|
73
|
-
.select('id, title, status, created_by, created_at, completed_at')
|
|
74
|
-
.eq('project_id', project_id)
|
|
75
|
-
.order('created_at', { ascending: false })
|
|
76
|
-
.limit(perTypeLimit);
|
|
77
|
-
|
|
78
|
-
if (created_by) query = query.eq('created_by', created_by);
|
|
79
|
-
if (since) query = query.gt('created_at', since);
|
|
80
56
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
type: 'task',
|
|
86
|
-
id: task.id,
|
|
87
|
-
title: task.title,
|
|
88
|
-
status: task.status,
|
|
89
|
-
created_by: task.created_by,
|
|
90
|
-
created_at: task.created_at,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Fetch progress logs if included
|
|
97
|
-
if (includeTypes.includes('progress')) {
|
|
98
|
-
let query = supabase
|
|
99
|
-
.from('progress_logs')
|
|
100
|
-
.select('id, summary, created_by, created_at')
|
|
101
|
-
.eq('project_id', project_id)
|
|
102
|
-
.order('created_at', { ascending: false })
|
|
103
|
-
.limit(perTypeLimit);
|
|
104
|
-
|
|
105
|
-
if (created_by) query = query.eq('created_by', created_by);
|
|
106
|
-
if (since) query = query.gt('created_at', since);
|
|
107
|
-
|
|
108
|
-
const { data: logs } = await query;
|
|
109
|
-
if (logs) {
|
|
110
|
-
for (const log of logs) {
|
|
111
|
-
activities.push({
|
|
112
|
-
type: 'progress',
|
|
113
|
-
id: log.id,
|
|
114
|
-
summary: log.summary,
|
|
115
|
-
created_by: log.created_by,
|
|
116
|
-
created_at: log.created_at,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Fetch blockers if included
|
|
123
|
-
if (includeTypes.includes('blocker')) {
|
|
124
|
-
let query = supabase
|
|
125
|
-
.from('blockers')
|
|
126
|
-
.select('id, description, status, created_by, created_at')
|
|
127
|
-
.eq('project_id', project_id)
|
|
128
|
-
.order('created_at', { ascending: false })
|
|
129
|
-
.limit(perTypeLimit);
|
|
57
|
+
const response = await apiClient.getActivityFeed(project_id, {
|
|
58
|
+
limit: effectiveLimit,
|
|
59
|
+
since
|
|
60
|
+
});
|
|
130
61
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const { data: blockers } = await query;
|
|
135
|
-
if (blockers) {
|
|
136
|
-
for (const blocker of blockers) {
|
|
137
|
-
activities.push({
|
|
138
|
-
type: 'blocker',
|
|
139
|
-
id: blocker.id,
|
|
140
|
-
description: blocker.description,
|
|
141
|
-
status: blocker.status,
|
|
142
|
-
created_by: blocker.created_by,
|
|
143
|
-
created_at: blocker.created_at,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Fetch decisions if included
|
|
150
|
-
if (includeTypes.includes('decision')) {
|
|
151
|
-
let query = supabase
|
|
152
|
-
.from('decisions')
|
|
153
|
-
.select('id, title, created_by, created_at')
|
|
154
|
-
.eq('project_id', project_id)
|
|
155
|
-
.order('created_at', { ascending: false })
|
|
156
|
-
.limit(perTypeLimit);
|
|
157
|
-
|
|
158
|
-
if (created_by) query = query.eq('created_by', created_by);
|
|
159
|
-
if (since) query = query.gt('created_at', since);
|
|
160
|
-
|
|
161
|
-
const { data: decisions } = await query;
|
|
162
|
-
if (decisions) {
|
|
163
|
-
for (const decision of decisions) {
|
|
164
|
-
activities.push({
|
|
165
|
-
type: 'decision',
|
|
166
|
-
id: decision.id,
|
|
167
|
-
title: decision.title,
|
|
168
|
-
created_by: decision.created_by,
|
|
169
|
-
created_at: decision.created_at,
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
}
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
throw new Error(`Failed to fetch activity feed: ${response.error}`);
|
|
173
64
|
}
|
|
174
65
|
|
|
175
|
-
|
|
176
|
-
activities.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
|
177
|
-
const limitedActivities = activities.slice(0, effectiveLimit);
|
|
178
|
-
|
|
179
|
-
return { result: { activities: limitedActivities } };
|
|
66
|
+
return { result: { activities: response.data?.activities || [] } };
|
|
180
67
|
};
|
|
181
68
|
|
|
182
69
|
/**
|