@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
package/src/cli.ts
CHANGED
|
@@ -10,20 +10,12 @@
|
|
|
10
10
|
* 2 = Error (allow exit with warning)
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { createClient } from '@supabase/supabase-js';
|
|
14
|
-
import { createHash } from 'crypto';
|
|
15
13
|
import { execSync } from 'child_process';
|
|
16
|
-
import { normalizeGitUrl } from './utils.js';
|
|
17
14
|
|
|
18
15
|
// ============================================================================
|
|
19
16
|
// Types
|
|
20
17
|
// ============================================================================
|
|
21
18
|
|
|
22
|
-
export interface AuthContext {
|
|
23
|
-
userId: string;
|
|
24
|
-
apiKeyId: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
19
|
export interface VerificationResult {
|
|
28
20
|
status: 'compliant' | 'non_compliant' | 'no_session' | 'error';
|
|
29
21
|
reason: string;
|
|
@@ -41,24 +33,34 @@ export interface VerificationResult {
|
|
|
41
33
|
};
|
|
42
34
|
}
|
|
43
35
|
|
|
44
|
-
interface TaskInfo {
|
|
45
|
-
id: string;
|
|
46
|
-
title: string;
|
|
47
|
-
progress_percentage: number;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
36
|
// ============================================================================
|
|
51
|
-
// Configuration
|
|
37
|
+
// Configuration (read at runtime for testability)
|
|
52
38
|
// ============================================================================
|
|
53
39
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
40
|
+
function getApiKey(): string | undefined {
|
|
41
|
+
return process.env.VIBESCOPE_API_KEY;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getApiUrl(): string {
|
|
45
|
+
return process.env.VIBESCOPE_API_URL || 'https://vibescope.dev';
|
|
46
|
+
}
|
|
57
47
|
|
|
58
48
|
// ============================================================================
|
|
59
49
|
// Git URL Detection
|
|
60
50
|
// ============================================================================
|
|
61
51
|
|
|
52
|
+
export function normalizeGitUrl(url: string): string {
|
|
53
|
+
// Remove .git suffix
|
|
54
|
+
let normalized = url.replace(/\.git$/, '');
|
|
55
|
+
// Convert SSH to HTTPS format
|
|
56
|
+
if (normalized.startsWith('git@')) {
|
|
57
|
+
normalized = normalized
|
|
58
|
+
.replace(/^git@/, 'https://')
|
|
59
|
+
.replace(/:([^/])/, '/$1');
|
|
60
|
+
}
|
|
61
|
+
return normalized;
|
|
62
|
+
}
|
|
63
|
+
|
|
62
64
|
export function detectGitUrl(): string | null {
|
|
63
65
|
try {
|
|
64
66
|
const url = execSync('git config --get remote.origin.url', {
|
|
@@ -67,47 +69,12 @@ export function detectGitUrl(): string | null {
|
|
|
67
69
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
68
70
|
}).trim();
|
|
69
71
|
|
|
70
|
-
// Normalize: remove .git suffix, convert SSH to HTTPS format
|
|
71
72
|
return normalizeGitUrl(url);
|
|
72
73
|
} catch {
|
|
73
74
|
return null;
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
// ============================================================================
|
|
78
|
-
// Authentication (reused from index.ts)
|
|
79
|
-
// ============================================================================
|
|
80
|
-
|
|
81
|
-
export function hashApiKey(key: string): string {
|
|
82
|
-
return createHash('sha256').update(key).digest('hex');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
|
-
export async function validateApiKey(
|
|
87
|
-
supabase: any,
|
|
88
|
-
apiKey: string
|
|
89
|
-
): Promise<AuthContext | null> {
|
|
90
|
-
const keyHash = hashApiKey(apiKey);
|
|
91
|
-
|
|
92
|
-
const { data, error } = await supabase
|
|
93
|
-
.from('api_keys')
|
|
94
|
-
.select('id, user_id')
|
|
95
|
-
.eq('key_hash', keyHash)
|
|
96
|
-
.single();
|
|
97
|
-
|
|
98
|
-
if (error || !data) {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Cast to expected shape since we're using untyped client
|
|
103
|
-
const row = data as { id: string; user_id: string };
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
userId: row.user_id,
|
|
107
|
-
apiKeyId: row.id,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
78
|
// ============================================================================
|
|
112
79
|
// Verification Logic
|
|
113
80
|
// ============================================================================
|
|
@@ -116,251 +83,41 @@ export async function verify(
|
|
|
116
83
|
gitUrl?: string,
|
|
117
84
|
projectId?: string
|
|
118
85
|
): Promise<VerificationResult> {
|
|
119
|
-
// Check environment
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
status: 'error',
|
|
123
|
-
reason: 'Missing SUPABASE_URL or SUPABASE_SERVICE_KEY environment variables',
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (!API_KEY) {
|
|
86
|
+
// Check environment (read at runtime for testability)
|
|
87
|
+
const apiKey = getApiKey();
|
|
88
|
+
if (!apiKey) {
|
|
128
89
|
return {
|
|
129
90
|
status: 'error',
|
|
130
91
|
reason: 'VIBESCOPE_API_KEY environment variable not set',
|
|
131
92
|
};
|
|
132
93
|
}
|
|
133
94
|
|
|
134
|
-
const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_KEY);
|
|
135
|
-
|
|
136
|
-
// Validate API key
|
|
137
|
-
const auth = await validateApiKey(supabase, API_KEY);
|
|
138
|
-
if (!auth) {
|
|
139
|
-
return {
|
|
140
|
-
status: 'error',
|
|
141
|
-
reason: 'Invalid VIBESCOPE_API_KEY',
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
95
|
// Auto-detect git URL if not provided
|
|
146
96
|
if (!gitUrl && !projectId) {
|
|
147
97
|
gitUrl = detectGitUrl() || undefined;
|
|
148
98
|
}
|
|
149
99
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (projectId) {
|
|
157
|
-
projectQuery = projectQuery.eq('id', projectId);
|
|
158
|
-
} else if (gitUrl) {
|
|
159
|
-
projectQuery = projectQuery.eq('git_url', gitUrl);
|
|
160
|
-
} else {
|
|
161
|
-
return {
|
|
162
|
-
status: 'no_session',
|
|
163
|
-
reason: 'Could not detect git URL and no project_id provided',
|
|
164
|
-
continuation_prompt:
|
|
165
|
-
'Could not detect which project you are working on. Please call start_work_session(git_url: "...") with your repository URL.',
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const { data: project, error: projectError } = await projectQuery.single();
|
|
170
|
-
|
|
171
|
-
if (projectError || !project) {
|
|
172
|
-
// Project not found - this is OK, might be an untracked repo
|
|
173
|
-
return {
|
|
174
|
-
status: 'compliant',
|
|
175
|
-
reason: `No Vibescope project found for ${gitUrl || projectId}. Untracked repository - exit allowed.`,
|
|
176
|
-
details: {
|
|
177
|
-
session_started: false,
|
|
178
|
-
project_id: null,
|
|
179
|
-
project_name: null,
|
|
180
|
-
git_url: gitUrl || null,
|
|
181
|
-
in_progress_tasks: 0,
|
|
182
|
-
tasks_completed_this_session: 0,
|
|
183
|
-
progress_logs_this_session: 0,
|
|
184
|
-
blockers_logged_this_session: 0,
|
|
185
|
-
session_duration_minutes: null,
|
|
186
|
-
},
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Check for agent session
|
|
191
|
-
const { data: session } = await supabase
|
|
192
|
-
.from('agent_sessions')
|
|
193
|
-
.select('id, last_synced_at, created_at')
|
|
194
|
-
.eq('api_key_id', auth.apiKeyId)
|
|
195
|
-
.eq('project_id', project.id)
|
|
196
|
-
.single();
|
|
197
|
-
|
|
198
|
-
if (!session) {
|
|
199
|
-
return {
|
|
200
|
-
status: 'non_compliant',
|
|
201
|
-
reason: 'No Vibescope session started for this project',
|
|
202
|
-
continuation_prompt: `[VIBESCOPE ENFORCEMENT] You have not started a Vibescope work session.
|
|
203
|
-
|
|
204
|
-
BEFORE you can exit, you MUST call:
|
|
205
|
-
start_work_session(git_url: "${project.git_url || project.id}")
|
|
206
|
-
|
|
207
|
-
This registers your session and gives you project context.`,
|
|
208
|
-
details: {
|
|
209
|
-
session_started: false,
|
|
210
|
-
project_id: project.id,
|
|
211
|
-
project_name: project.name,
|
|
212
|
-
git_url: project.git_url,
|
|
213
|
-
in_progress_tasks: 0,
|
|
214
|
-
tasks_completed_this_session: 0,
|
|
215
|
-
progress_logs_this_session: 0,
|
|
216
|
-
blockers_logged_this_session: 0,
|
|
217
|
-
session_duration_minutes: null,
|
|
218
|
-
},
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const sessionStartTime = new Date(session.created_at);
|
|
223
|
-
const lastSyncTime = new Date(session.last_synced_at);
|
|
224
|
-
const now = new Date();
|
|
225
|
-
const sessionDurationMinutes = Math.round(
|
|
226
|
-
(now.getTime() - sessionStartTime.getTime()) / 60000
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
// Grace period: if session is less than 1 minute, allow exit
|
|
230
|
-
if (sessionDurationMinutes < 1) {
|
|
231
|
-
return {
|
|
232
|
-
status: 'compliant',
|
|
233
|
-
reason: 'Very short session (< 1 minute) - exit allowed',
|
|
234
|
-
details: {
|
|
235
|
-
session_started: true,
|
|
236
|
-
project_id: project.id,
|
|
237
|
-
project_name: project.name,
|
|
238
|
-
git_url: project.git_url,
|
|
239
|
-
in_progress_tasks: 0,
|
|
240
|
-
tasks_completed_this_session: 0,
|
|
241
|
-
progress_logs_this_session: 0,
|
|
242
|
-
blockers_logged_this_session: 0,
|
|
243
|
-
session_duration_minutes: sessionDurationMinutes,
|
|
244
|
-
},
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Check for in-progress tasks
|
|
249
|
-
const { data: inProgressTasks } = await supabase
|
|
250
|
-
.from('tasks')
|
|
251
|
-
.select('id, title, progress_percentage')
|
|
252
|
-
.eq('project_id', project.id)
|
|
253
|
-
.eq('status', 'in_progress');
|
|
254
|
-
|
|
255
|
-
const inProgressCount = inProgressTasks?.length || 0;
|
|
256
|
-
|
|
257
|
-
if (inProgressCount > 0) {
|
|
258
|
-
const taskList = (inProgressTasks as TaskInfo[])
|
|
259
|
-
.map((t) => ` - ${t.title} (${t.progress_percentage}% complete)`)
|
|
260
|
-
.join('\n');
|
|
261
|
-
|
|
262
|
-
return {
|
|
263
|
-
status: 'non_compliant',
|
|
264
|
-
reason: `You have ${inProgressCount} task(s) still in_progress`,
|
|
265
|
-
continuation_prompt: `[VIBESCOPE ENFORCEMENT] You have ${inProgressCount} task(s) still marked as in_progress:
|
|
266
|
-
|
|
267
|
-
${taskList}
|
|
268
|
-
|
|
269
|
-
You MUST either:
|
|
270
|
-
1. Complete them: complete_task(task_id: "...", summary: "...")
|
|
271
|
-
2. Log why you're stopping: log_progress(project_id: "${project.id}", summary: "Stopping because...")`,
|
|
272
|
-
details: {
|
|
273
|
-
session_started: true,
|
|
274
|
-
project_id: project.id,
|
|
275
|
-
project_name: project.name,
|
|
276
|
-
git_url: project.git_url,
|
|
277
|
-
in_progress_tasks: inProgressCount,
|
|
278
|
-
tasks_completed_this_session: 0,
|
|
279
|
-
progress_logs_this_session: 0,
|
|
280
|
-
blockers_logged_this_session: 0,
|
|
281
|
-
session_duration_minutes: sessionDurationMinutes,
|
|
100
|
+
try {
|
|
101
|
+
const response = await fetch(`${getApiUrl()}/api/mcp/verify`, {
|
|
102
|
+
method: 'POST',
|
|
103
|
+
headers: {
|
|
104
|
+
'Content-Type': 'application/json',
|
|
282
105
|
},
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
.select('id', { count: 'exact' })
|
|
294
|
-
.eq('project_id', project.id)
|
|
295
|
-
.eq('status', 'completed')
|
|
296
|
-
.gte('completed_at', sessionStartIso),
|
|
297
|
-
|
|
298
|
-
// Progress logs by agent after session start
|
|
299
|
-
supabase
|
|
300
|
-
.from('progress_logs')
|
|
301
|
-
.select('id', { count: 'exact' })
|
|
302
|
-
.eq('project_id', project.id)
|
|
303
|
-
.eq('created_by', 'agent')
|
|
304
|
-
.gte('created_at', sessionStartIso),
|
|
305
|
-
|
|
306
|
-
// Blockers logged by agent after session start
|
|
307
|
-
supabase
|
|
308
|
-
.from('blockers')
|
|
309
|
-
.select('id', { count: 'exact' })
|
|
310
|
-
.eq('project_id', project.id)
|
|
311
|
-
.eq('created_by', 'agent')
|
|
312
|
-
.gte('created_at', sessionStartIso),
|
|
313
|
-
]);
|
|
314
|
-
|
|
315
|
-
const tasksCompleted = completedResult.count || 0;
|
|
316
|
-
const progressLogsCount = progressResult.count || 0;
|
|
317
|
-
const blockersLogged = blockerResult.count || 0;
|
|
318
|
-
|
|
319
|
-
// Check if any tracked work was done
|
|
320
|
-
const anyWorkTracked =
|
|
321
|
-
tasksCompleted > 0 || progressLogsCount > 0 || blockersLogged > 0;
|
|
322
|
-
|
|
323
|
-
if (!anyWorkTracked && sessionDurationMinutes >= 5) {
|
|
106
|
+
body: JSON.stringify({
|
|
107
|
+
api_key: apiKey,
|
|
108
|
+
git_url: gitUrl,
|
|
109
|
+
project_id: projectId,
|
|
110
|
+
}),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const result = await response.json() as VerificationResult;
|
|
114
|
+
return result;
|
|
115
|
+
} catch (err) {
|
|
324
116
|
return {
|
|
325
|
-
status: '
|
|
326
|
-
reason:
|
|
327
|
-
continuation_prompt: `[VIBESCOPE ENFORCEMENT] Your session has been active for ${sessionDurationMinutes} minutes but no work was tracked.
|
|
328
|
-
|
|
329
|
-
The human is watching the dashboard and will see an empty session.
|
|
330
|
-
|
|
331
|
-
You MUST either:
|
|
332
|
-
1. Pick a task and work on it: get_next_task(project_id: "${project.id}")
|
|
333
|
-
2. Log why you did nothing: log_progress(project_id: "${project.id}", summary: "No work done because...")`,
|
|
334
|
-
details: {
|
|
335
|
-
session_started: true,
|
|
336
|
-
project_id: project.id,
|
|
337
|
-
project_name: project.name,
|
|
338
|
-
git_url: project.git_url,
|
|
339
|
-
in_progress_tasks: 0,
|
|
340
|
-
tasks_completed_this_session: tasksCompleted,
|
|
341
|
-
progress_logs_this_session: progressLogsCount,
|
|
342
|
-
blockers_logged_this_session: blockersLogged,
|
|
343
|
-
session_duration_minutes: sessionDurationMinutes,
|
|
344
|
-
},
|
|
117
|
+
status: 'error',
|
|
118
|
+
reason: err instanceof Error ? err.message : 'Network error',
|
|
345
119
|
};
|
|
346
120
|
}
|
|
347
|
-
|
|
348
|
-
// All checks passed - compliant!
|
|
349
|
-
return {
|
|
350
|
-
status: 'compliant',
|
|
351
|
-
reason: 'All tracked work completed properly',
|
|
352
|
-
details: {
|
|
353
|
-
session_started: true,
|
|
354
|
-
project_id: project.id,
|
|
355
|
-
project_name: project.name,
|
|
356
|
-
git_url: project.git_url,
|
|
357
|
-
in_progress_tasks: 0,
|
|
358
|
-
tasks_completed_this_session: tasksCompleted,
|
|
359
|
-
progress_logs_this_session: progressLogsCount,
|
|
360
|
-
blockers_logged_this_session: blockersLogged,
|
|
361
|
-
session_duration_minutes: sessionDurationMinutes,
|
|
362
|
-
},
|
|
363
|
-
};
|
|
364
121
|
}
|
|
365
122
|
|
|
366
123
|
// ============================================================================
|
|
@@ -413,8 +170,7 @@ Exit Codes:
|
|
|
413
170
|
|
|
414
171
|
Environment Variables:
|
|
415
172
|
VIBESCOPE_API_KEY Required - Your Vibescope API key
|
|
416
|
-
|
|
417
|
-
SUPABASE_SERVICE_KEY Required - Supabase service role key
|
|
173
|
+
VIBESCOPE_API_URL Optional - API URL (default: https://vibescope.dev)
|
|
418
174
|
`);
|
|
419
175
|
process.exit(0);
|
|
420
176
|
} else {
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global Test Setup for Handler Tests
|
|
3
|
+
*
|
|
4
|
+
* This file is loaded before all tests via vitest.config.ts setupFiles.
|
|
5
|
+
* It provides a global mock for the API client used by migrated handlers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { vi, beforeEach } from 'vitest';
|
|
9
|
+
|
|
10
|
+
// Default success response for API calls
|
|
11
|
+
const defaultSuccess = { ok: true, data: {} };
|
|
12
|
+
const defaultError = { ok: false, error: 'Mock error' };
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Global Mock API Client
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Mock API client with all methods stubbed.
|
|
20
|
+
* Tests can configure return values using mockResolvedValue/mockImplementation.
|
|
21
|
+
* Default return is a success response { ok: true, data: {} }
|
|
22
|
+
*/
|
|
23
|
+
export const mockApiClient = {
|
|
24
|
+
// Session
|
|
25
|
+
startSession: vi.fn(),
|
|
26
|
+
heartbeat: vi.fn(),
|
|
27
|
+
syncSession: vi.fn(),
|
|
28
|
+
endSession: vi.fn(),
|
|
29
|
+
|
|
30
|
+
// Project
|
|
31
|
+
listProjects: vi.fn(),
|
|
32
|
+
getProject: vi.fn(),
|
|
33
|
+
getProjectContext: vi.fn(),
|
|
34
|
+
createProject: vi.fn(),
|
|
35
|
+
updateProject: vi.fn(),
|
|
36
|
+
updateProjectReadme: vi.fn(),
|
|
37
|
+
getGitWorkflow: vi.fn(),
|
|
38
|
+
|
|
39
|
+
// Tasks
|
|
40
|
+
getTasks: vi.fn(),
|
|
41
|
+
getNextTask: vi.fn(),
|
|
42
|
+
createTask: vi.fn(),
|
|
43
|
+
updateTask: vi.fn(),
|
|
44
|
+
completeTask: vi.fn(),
|
|
45
|
+
deleteTask: vi.fn(),
|
|
46
|
+
addTaskReference: vi.fn(),
|
|
47
|
+
removeTaskReference: vi.fn(),
|
|
48
|
+
batchUpdateTasks: vi.fn(),
|
|
49
|
+
batchCompleteTasks: vi.fn(),
|
|
50
|
+
|
|
51
|
+
// Subtasks
|
|
52
|
+
addSubtask: vi.fn(),
|
|
53
|
+
getSubtasks: vi.fn(),
|
|
54
|
+
|
|
55
|
+
// Progress
|
|
56
|
+
logProgress: vi.fn(),
|
|
57
|
+
getActivityFeed: vi.fn(),
|
|
58
|
+
|
|
59
|
+
// Blockers
|
|
60
|
+
addBlocker: vi.fn(),
|
|
61
|
+
resolveBlocker: vi.fn(),
|
|
62
|
+
getBlockers: vi.fn(),
|
|
63
|
+
deleteBlocker: vi.fn(),
|
|
64
|
+
|
|
65
|
+
// Decisions
|
|
66
|
+
logDecision: vi.fn(),
|
|
67
|
+
getDecisions: vi.fn(),
|
|
68
|
+
deleteDecision: vi.fn(),
|
|
69
|
+
|
|
70
|
+
// Ideas
|
|
71
|
+
addIdea: vi.fn(),
|
|
72
|
+
updateIdea: vi.fn(),
|
|
73
|
+
getIdeas: vi.fn(),
|
|
74
|
+
deleteIdea: vi.fn(),
|
|
75
|
+
convertIdeaToTask: vi.fn(),
|
|
76
|
+
|
|
77
|
+
// Findings
|
|
78
|
+
addFinding: vi.fn(),
|
|
79
|
+
getFindings: vi.fn(),
|
|
80
|
+
getFindingsStats: vi.fn(),
|
|
81
|
+
updateFinding: vi.fn(),
|
|
82
|
+
deleteFinding: vi.fn(),
|
|
83
|
+
|
|
84
|
+
// Requests
|
|
85
|
+
getPendingRequests: vi.fn(),
|
|
86
|
+
acknowledgeRequest: vi.fn(),
|
|
87
|
+
answerQuestion: vi.fn(),
|
|
88
|
+
|
|
89
|
+
// Milestones
|
|
90
|
+
addMilestone: vi.fn(),
|
|
91
|
+
updateMilestone: vi.fn(),
|
|
92
|
+
completeMilestone: vi.fn(),
|
|
93
|
+
deleteMilestone: vi.fn(),
|
|
94
|
+
getMilestones: vi.fn(),
|
|
95
|
+
|
|
96
|
+
// Fallback
|
|
97
|
+
startFallbackActivity: vi.fn(),
|
|
98
|
+
stopFallbackActivity: vi.fn(),
|
|
99
|
+
getActivityHistory: vi.fn(),
|
|
100
|
+
getActivitySchedules: vi.fn(),
|
|
101
|
+
|
|
102
|
+
// Cost
|
|
103
|
+
getCostSummary: vi.fn(),
|
|
104
|
+
getCostAlerts: vi.fn(),
|
|
105
|
+
addCostAlert: vi.fn(),
|
|
106
|
+
updateCostAlert: vi.fn(),
|
|
107
|
+
deleteCostAlert: vi.fn(),
|
|
108
|
+
getTaskCosts: vi.fn(),
|
|
109
|
+
|
|
110
|
+
// Git Issues
|
|
111
|
+
addGitIssue: vi.fn(),
|
|
112
|
+
resolveGitIssue: vi.fn(),
|
|
113
|
+
getGitIssues: vi.fn(),
|
|
114
|
+
deleteGitIssue: vi.fn(),
|
|
115
|
+
|
|
116
|
+
// Organizations
|
|
117
|
+
listOrganizations: vi.fn(),
|
|
118
|
+
createOrganization: vi.fn(),
|
|
119
|
+
updateOrganization: vi.fn(),
|
|
120
|
+
deleteOrganization: vi.fn(),
|
|
121
|
+
listOrgMembers: vi.fn(),
|
|
122
|
+
inviteMember: vi.fn(),
|
|
123
|
+
updateMemberRole: vi.fn(),
|
|
124
|
+
removeMember: vi.fn(),
|
|
125
|
+
leaveOrganization: vi.fn(),
|
|
126
|
+
shareProjectWithOrg: vi.fn(),
|
|
127
|
+
updateProjectShare: vi.fn(),
|
|
128
|
+
unshareProject: vi.fn(),
|
|
129
|
+
listProjectShares: vi.fn(),
|
|
130
|
+
|
|
131
|
+
// Bodies of Work
|
|
132
|
+
createBodyOfWork: vi.fn(),
|
|
133
|
+
updateBodyOfWork: vi.fn(),
|
|
134
|
+
getBodyOfWork: vi.fn(),
|
|
135
|
+
getBodiesOfWork: vi.fn(),
|
|
136
|
+
deleteBodyOfWork: vi.fn(),
|
|
137
|
+
addTaskToBodyOfWork: vi.fn(),
|
|
138
|
+
removeTaskFromBodyOfWork: vi.fn(),
|
|
139
|
+
activateBodyOfWork: vi.fn(),
|
|
140
|
+
addTaskDependency: vi.fn(),
|
|
141
|
+
removeTaskDependency: vi.fn(),
|
|
142
|
+
getTaskDependencies: vi.fn(),
|
|
143
|
+
getNextBodyOfWorkTask: vi.fn(),
|
|
144
|
+
|
|
145
|
+
// Deployment
|
|
146
|
+
requestDeployment: vi.fn(),
|
|
147
|
+
claimDeploymentValidation: vi.fn(),
|
|
148
|
+
reportValidation: vi.fn(),
|
|
149
|
+
checkDeploymentStatus: vi.fn(),
|
|
150
|
+
startDeployment: vi.fn(),
|
|
151
|
+
completeDeployment: vi.fn(),
|
|
152
|
+
cancelDeployment: vi.fn(),
|
|
153
|
+
addDeploymentRequirement: vi.fn(),
|
|
154
|
+
completeDeploymentRequirement: vi.fn(),
|
|
155
|
+
getDeploymentRequirements: vi.fn(),
|
|
156
|
+
scheduleDeployment: vi.fn(),
|
|
157
|
+
getScheduledDeployments: vi.fn(),
|
|
158
|
+
updateScheduledDeployment: vi.fn(),
|
|
159
|
+
deleteScheduledDeployment: vi.fn(),
|
|
160
|
+
triggerScheduledDeployment: vi.fn(),
|
|
161
|
+
checkDueDeployments: vi.fn(),
|
|
162
|
+
|
|
163
|
+
// Validation
|
|
164
|
+
getTasksAwaitingValidation: vi.fn(),
|
|
165
|
+
claimValidation: vi.fn(),
|
|
166
|
+
validateTask: vi.fn(),
|
|
167
|
+
|
|
168
|
+
// Help Topics
|
|
169
|
+
getHelpTopic: vi.fn(),
|
|
170
|
+
getHelpTopics: vi.fn(),
|
|
171
|
+
|
|
172
|
+
// Proxy (generic)
|
|
173
|
+
proxy: vi.fn(),
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// ============================================================================
|
|
177
|
+
// Mock the API Client Module
|
|
178
|
+
// ============================================================================
|
|
179
|
+
|
|
180
|
+
// Note: The path is relative to the test file location, not this setup file
|
|
181
|
+
// This setup file is in src/handlers/, tests import from ../api-client.js
|
|
182
|
+
vi.mock('../api-client.js', () => ({
|
|
183
|
+
getApiClient: () => mockApiClient,
|
|
184
|
+
initApiClient: vi.fn(),
|
|
185
|
+
VibescopeApiClient: vi.fn(),
|
|
186
|
+
}));
|
|
187
|
+
|
|
188
|
+
// Also mock with absolute-style path for tests outside handlers/
|
|
189
|
+
vi.mock('../../api-client.js', () => ({
|
|
190
|
+
getApiClient: () => mockApiClient,
|
|
191
|
+
initApiClient: vi.fn(),
|
|
192
|
+
VibescopeApiClient: vi.fn(),
|
|
193
|
+
}));
|
|
194
|
+
|
|
195
|
+
// Export for test files that need direct access
|
|
196
|
+
export { mockApiClient as __mockApiClient__ };
|
|
197
|
+
|
|
198
|
+
// ============================================================================
|
|
199
|
+
// Reset Mocks Before Each Test
|
|
200
|
+
// ============================================================================
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Reset all mock functions before each test and set default return values.
|
|
204
|
+
* This ensures tests start with a clean slate and have sensible defaults.
|
|
205
|
+
*/
|
|
206
|
+
beforeEach(() => {
|
|
207
|
+
// Reset all mocks
|
|
208
|
+
Object.values(mockApiClient).forEach((mockFn) => {
|
|
209
|
+
if (typeof mockFn === 'function' && mockFn.mockReset) {
|
|
210
|
+
mockFn.mockReset();
|
|
211
|
+
// Set default return value to success
|
|
212
|
+
mockFn.mockResolvedValue({ ok: true, data: {} });
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
});
|