@vibecodetown/mcp-server 2.1.4 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/build/auth/credential_store.js +146 -0
- package/build/auth/public_key.js +6 -4
- package/build/bootstrap/doctor.js +113 -5
- package/build/bootstrap/installer.js +85 -15
- package/build/bootstrap/registry.js +11 -6
- package/build/bootstrap/skills-installer.js +365 -0
- package/build/control_plane/gate.js +52 -70
- package/build/dx/activity.js +26 -3
- package/build/engine.js +151 -0
- package/build/errors.js +107 -0
- package/build/generated/bridge_build_seed_input.js +2 -0
- package/build/generated/bridge_build_seed_output.js +2 -0
- package/build/generated/bridge_confirm_reference_input.js +2 -0
- package/build/generated/bridge_confirm_reference_output.js +2 -0
- package/build/generated/bridge_confirmed_reference_file.js +2 -0
- package/build/generated/bridge_generate_references_input.js +2 -0
- package/build/generated/bridge_generate_references_output.js +2 -0
- package/build/generated/bridge_references_file.js +2 -0
- package/build/generated/bridge_work_order_seed_file.js +2 -0
- package/build/generated/contracts_bundle_info.js +3 -3
- package/build/generated/index.js +14 -0
- package/build/generated/ingress_input.js +2 -0
- package/build/generated/ingress_output.js +2 -0
- package/build/generated/ingress_resolution_file.js +2 -0
- package/build/generated/ingress_summary_file.js +2 -0
- package/build/generated/message_template_id_mapping_file.js +2 -0
- package/build/generated/run_app_input.js +1 -1
- package/build/index.js +4 -1
- package/build/local-mode/git.js +36 -22
- package/build/local-mode/paths.js +1 -0
- package/build/local-mode/project-state.js +176 -0
- package/build/local-mode/setup.js +21 -1
- package/build/local-mode/templates.js +3 -3
- package/build/path-utils.js +68 -0
- package/build/runtime/cli_invoker.js +416 -0
- package/build/tools/vibe_pm/advisory_review.js +5 -3
- package/build/tools/vibe_pm/bridge_build_seed.js +164 -0
- package/build/tools/vibe_pm/bridge_confirm_reference.js +91 -0
- package/build/tools/vibe_pm/bridge_generate_references.js +258 -0
- package/build/tools/vibe_pm/briefing.js +26 -1
- package/build/tools/vibe_pm/context.js +79 -0
- package/build/tools/vibe_pm/create_work_order.js +200 -3
- package/build/tools/vibe_pm/doctor.js +95 -0
- package/build/tools/vibe_pm/entity_gate/preflight.js +8 -3
- package/build/tools/vibe_pm/export_output.js +14 -13
- package/build/tools/vibe_pm/finalize_work.js +74 -0
- package/build/tools/vibe_pm/force_override.js +104 -0
- package/build/tools/vibe_pm/get_decision.js +2 -2
- package/build/tools/vibe_pm/index.js +160 -3
- package/build/tools/vibe_pm/ingress.js +645 -0
- package/build/tools/vibe_pm/ingress_gate.js +116 -0
- package/build/tools/vibe_pm/inspect_code.js +90 -20
- package/build/tools/vibe_pm/kce/doc_usage.js +4 -9
- package/build/tools/vibe_pm/kce/on_finalize.js +2 -2
- package/build/tools/vibe_pm/kce/preflight.js +11 -7
- package/build/tools/vibe_pm/list_rules.js +135 -0
- package/build/tools/vibe_pm/memory_status.js +11 -8
- package/build/tools/vibe_pm/memory_sync.js +11 -8
- package/build/tools/vibe_pm/pm_language.js +17 -16
- package/build/tools/vibe_pm/pre_commit_analysis.js +292 -0
- package/build/tools/vibe_pm/publish_mcp.js +271 -0
- package/build/tools/vibe_pm/python_error.js +115 -0
- package/build/tools/vibe_pm/run_app.js +215 -86
- package/build/tools/vibe_pm/run_app_podman.js +64 -2
- package/build/tools/vibe_pm/save_rule.js +120 -0
- package/build/tools/vibe_pm/search_oss.js +5 -3
- package/build/tools/vibe_pm/spec_rag.js +185 -0
- package/build/tools/vibe_pm/status.js +50 -3
- package/build/tools/vibe_pm/submit_decision.js +2 -2
- package/build/tools/vibe_pm/types.js +28 -0
- package/build/tools/vibe_pm/undo_last_task.js +23 -20
- package/build/tools/vibe_pm/waiter_mapping.js +155 -0
- package/build/tools/vibe_pm/zoekt_evidence.js +5 -3
- package/build/tools.js +13 -5
- package/build/version-check.js +5 -5
- package/build/vibe-cli.js +742 -39
- package/package.json +5 -4
- package/skills/VRIP_INSTALL_MANIFEST_DOCTOR.skill.md +288 -0
- package/skills/index.json +14 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/bootstrap/skills-installer.ts
|
|
2
|
+
// Skill bundle installation, activation, and health check
|
|
3
|
+
// Skills are prebundled in the npm package (no network required)
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import crypto from "node:crypto";
|
|
7
|
+
import { SKILL_SPEC } from "./registry.js";
|
|
8
|
+
// ============================================================
|
|
9
|
+
// Path Utilities
|
|
10
|
+
// ============================================================
|
|
11
|
+
/**
|
|
12
|
+
* Find package root by looking for package.json
|
|
13
|
+
*/
|
|
14
|
+
function findPackageRoot() {
|
|
15
|
+
// Start from current module's directory
|
|
16
|
+
let dir = path.dirname(new URL(import.meta.url).pathname);
|
|
17
|
+
// On Windows, remove leading slash from /C:/...
|
|
18
|
+
if (process.platform === "win32" && dir.startsWith("/")) {
|
|
19
|
+
dir = dir.slice(1);
|
|
20
|
+
}
|
|
21
|
+
for (let i = 0; i < 10; i++) {
|
|
22
|
+
const pkgPath = path.join(dir, "package.json");
|
|
23
|
+
try {
|
|
24
|
+
if (fs.existsSync(pkgPath)) {
|
|
25
|
+
return dir;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// ignore
|
|
30
|
+
}
|
|
31
|
+
const parent = path.dirname(dir);
|
|
32
|
+
if (parent === dir)
|
|
33
|
+
break;
|
|
34
|
+
dir = parent;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get the path to the prebundled skills directory in the npm package
|
|
40
|
+
*/
|
|
41
|
+
export function getSkillsBundlePath() {
|
|
42
|
+
const pkgRoot = findPackageRoot();
|
|
43
|
+
if (!pkgRoot)
|
|
44
|
+
return null;
|
|
45
|
+
const skillsDir = path.join(pkgRoot, SKILL_SPEC.bundlePath);
|
|
46
|
+
try {
|
|
47
|
+
if (fs.existsSync(skillsDir) && fs.statSync(skillsDir).isDirectory()) {
|
|
48
|
+
return skillsDir;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// ignore
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get the path to the project's .vibe/skills/ directory
|
|
58
|
+
*/
|
|
59
|
+
export function getProjectSkillsPath(projectRoot) {
|
|
60
|
+
const root = projectRoot || process.cwd();
|
|
61
|
+
return path.join(root, ".vibe", "skills");
|
|
62
|
+
}
|
|
63
|
+
// ============================================================
|
|
64
|
+
// Index Loading
|
|
65
|
+
// ============================================================
|
|
66
|
+
/**
|
|
67
|
+
* Load the skills index from the prebundled directory
|
|
68
|
+
*/
|
|
69
|
+
export function loadSkillsIndex() {
|
|
70
|
+
const bundlePath = getSkillsBundlePath();
|
|
71
|
+
if (!bundlePath)
|
|
72
|
+
return null;
|
|
73
|
+
const indexPath = path.join(bundlePath, "index.json");
|
|
74
|
+
try {
|
|
75
|
+
const content = fs.readFileSync(indexPath, "utf-8");
|
|
76
|
+
return JSON.parse(content);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Load the activated skills index from the project
|
|
84
|
+
*/
|
|
85
|
+
export function loadActivatedSkillsIndex(projectRoot) {
|
|
86
|
+
const skillsPath = getProjectSkillsPath(projectRoot);
|
|
87
|
+
const indexPath = path.join(skillsPath, "index.json");
|
|
88
|
+
try {
|
|
89
|
+
const content = fs.readFileSync(indexPath, "utf-8");
|
|
90
|
+
return JSON.parse(content);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// ============================================================
|
|
97
|
+
// Health Check
|
|
98
|
+
// ============================================================
|
|
99
|
+
/**
|
|
100
|
+
* Calculate SHA256 hash of a file
|
|
101
|
+
*/
|
|
102
|
+
function calculateSha256(filePath) {
|
|
103
|
+
try {
|
|
104
|
+
const content = fs.readFileSync(filePath);
|
|
105
|
+
return crypto.createHash("sha256").update(content).digest("hex");
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Check health of a single skill in the bundle
|
|
113
|
+
*/
|
|
114
|
+
function checkSkillHealth(bundlePath, skill) {
|
|
115
|
+
const filePath = path.join(bundlePath, skill.file);
|
|
116
|
+
if (!fs.existsSync(filePath)) {
|
|
117
|
+
return {
|
|
118
|
+
id: skill.id,
|
|
119
|
+
status: "missing",
|
|
120
|
+
version: skill.version,
|
|
121
|
+
path: null,
|
|
122
|
+
error: `Skill file not found: ${skill.file}`
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const actualSha = calculateSha256(filePath);
|
|
126
|
+
if (!actualSha) {
|
|
127
|
+
return {
|
|
128
|
+
id: skill.id,
|
|
129
|
+
status: "sha_mismatch",
|
|
130
|
+
version: skill.version,
|
|
131
|
+
path: filePath,
|
|
132
|
+
error: "Failed to calculate SHA256"
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (actualSha !== skill.sha256) {
|
|
136
|
+
return {
|
|
137
|
+
id: skill.id,
|
|
138
|
+
status: "sha_mismatch",
|
|
139
|
+
version: skill.version,
|
|
140
|
+
path: filePath,
|
|
141
|
+
error: `SHA256 mismatch: expected ${skill.sha256}, got ${actualSha}`
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
id: skill.id,
|
|
146
|
+
status: "ok",
|
|
147
|
+
version: skill.version,
|
|
148
|
+
path: filePath
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Check health of the entire skills bundle
|
|
153
|
+
*/
|
|
154
|
+
export function getSkillsHealth() {
|
|
155
|
+
const bundlePath = getSkillsBundlePath();
|
|
156
|
+
if (!bundlePath) {
|
|
157
|
+
return {
|
|
158
|
+
installed: false,
|
|
159
|
+
bundlePath: null,
|
|
160
|
+
version: null,
|
|
161
|
+
skills: [],
|
|
162
|
+
summary: { total: 0, ok: 0, missing: 0, corrupted: 0 }
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const index = loadSkillsIndex();
|
|
166
|
+
if (!index) {
|
|
167
|
+
return {
|
|
168
|
+
installed: true,
|
|
169
|
+
bundlePath,
|
|
170
|
+
version: null,
|
|
171
|
+
skills: [],
|
|
172
|
+
summary: { total: 0, ok: 0, missing: 0, corrupted: 0 }
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const skills = index.skills.map(skill => checkSkillHealth(bundlePath, skill));
|
|
176
|
+
const summary = {
|
|
177
|
+
total: skills.length,
|
|
178
|
+
ok: skills.filter(s => s.status === "ok").length,
|
|
179
|
+
missing: skills.filter(s => s.status === "missing").length,
|
|
180
|
+
corrupted: skills.filter(s => s.status === "sha_mismatch").length
|
|
181
|
+
};
|
|
182
|
+
return {
|
|
183
|
+
installed: true,
|
|
184
|
+
bundlePath,
|
|
185
|
+
version: index.version,
|
|
186
|
+
skills,
|
|
187
|
+
summary
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
// ============================================================
|
|
191
|
+
// Skill Listing
|
|
192
|
+
// ============================================================
|
|
193
|
+
/**
|
|
194
|
+
* List all available skills from the prebundled directory
|
|
195
|
+
*/
|
|
196
|
+
export function listAvailableSkills() {
|
|
197
|
+
const index = loadSkillsIndex();
|
|
198
|
+
return index?.skills ?? [];
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* List activated skills in the project
|
|
202
|
+
*/
|
|
203
|
+
export function listActivatedSkills(projectRoot) {
|
|
204
|
+
const index = loadActivatedSkillsIndex(projectRoot);
|
|
205
|
+
return index?.activated ?? [];
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Get skill metadata by ID
|
|
209
|
+
*/
|
|
210
|
+
export function getSkillById(skillId) {
|
|
211
|
+
const skills = listAvailableSkills();
|
|
212
|
+
return skills.find(s => s.id === skillId) ?? null;
|
|
213
|
+
}
|
|
214
|
+
// ============================================================
|
|
215
|
+
// Skill Activation
|
|
216
|
+
// ============================================================
|
|
217
|
+
/**
|
|
218
|
+
* Activate skills for a project by copying them to .vibe/skills/
|
|
219
|
+
*/
|
|
220
|
+
export async function activateSkills(projectRoot, skillIds) {
|
|
221
|
+
const bundlePath = getSkillsBundlePath();
|
|
222
|
+
if (!bundlePath) {
|
|
223
|
+
return { activated: [], errors: ["Skill bundle not found in package"] };
|
|
224
|
+
}
|
|
225
|
+
const index = loadSkillsIndex();
|
|
226
|
+
if (!index) {
|
|
227
|
+
return { activated: [], errors: ["Skills index not found or invalid"] };
|
|
228
|
+
}
|
|
229
|
+
// If no skillIds specified, activate all
|
|
230
|
+
const toActivate = skillIds ?? index.skills.map(s => s.id);
|
|
231
|
+
const skillsToActivate = toActivate
|
|
232
|
+
.map(id => index.skills.find(s => s.id === id))
|
|
233
|
+
.filter((s) => s !== undefined);
|
|
234
|
+
if (skillsToActivate.length === 0) {
|
|
235
|
+
return { activated: [], errors: ["No valid skills to activate"] };
|
|
236
|
+
}
|
|
237
|
+
const projectSkillsPath = getProjectSkillsPath(projectRoot);
|
|
238
|
+
// Ensure .vibe/skills/ directory exists
|
|
239
|
+
try {
|
|
240
|
+
await fs.promises.mkdir(projectSkillsPath, { recursive: true });
|
|
241
|
+
}
|
|
242
|
+
catch (e) {
|
|
243
|
+
return {
|
|
244
|
+
activated: [],
|
|
245
|
+
errors: [`Failed to create skills directory: ${e instanceof Error ? e.message : String(e)}`]
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
const activated = [];
|
|
249
|
+
const errors = [];
|
|
250
|
+
// Copy each skill file
|
|
251
|
+
for (const skill of skillsToActivate) {
|
|
252
|
+
const srcPath = path.join(bundlePath, skill.file);
|
|
253
|
+
const destPath = path.join(projectSkillsPath, skill.file);
|
|
254
|
+
try {
|
|
255
|
+
await fs.promises.copyFile(srcPath, destPath);
|
|
256
|
+
activated.push(skill.id);
|
|
257
|
+
}
|
|
258
|
+
catch (e) {
|
|
259
|
+
errors.push(`Failed to copy ${skill.id}: ${e instanceof Error ? e.message : String(e)}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Write the activated skills index
|
|
263
|
+
if (activated.length > 0) {
|
|
264
|
+
const activatedIndex = {
|
|
265
|
+
version: index.version,
|
|
266
|
+
activated,
|
|
267
|
+
activated_at: new Date().toISOString(),
|
|
268
|
+
source: "prebundled",
|
|
269
|
+
skills: skillsToActivate.filter(s => activated.includes(s.id))
|
|
270
|
+
};
|
|
271
|
+
const indexPath = path.join(projectSkillsPath, "index.json");
|
|
272
|
+
try {
|
|
273
|
+
await fs.promises.writeFile(indexPath, JSON.stringify(activatedIndex, null, 2), "utf-8");
|
|
274
|
+
}
|
|
275
|
+
catch (e) {
|
|
276
|
+
errors.push(`Failed to write index.json: ${e instanceof Error ? e.message : String(e)}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return { activated, errors };
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Deactivate a skill from the project
|
|
283
|
+
*/
|
|
284
|
+
export async function deactivateSkill(projectRoot, skillId) {
|
|
285
|
+
const projectSkillsPath = getProjectSkillsPath(projectRoot);
|
|
286
|
+
const activatedIndex = loadActivatedSkillsIndex(projectRoot);
|
|
287
|
+
if (!activatedIndex) {
|
|
288
|
+
return { success: false, error: "No activated skills found" };
|
|
289
|
+
}
|
|
290
|
+
const skillMeta = activatedIndex.skills.find(s => s.id === skillId);
|
|
291
|
+
if (!skillMeta) {
|
|
292
|
+
return { success: false, error: `Skill ${skillId} is not activated` };
|
|
293
|
+
}
|
|
294
|
+
// Remove the skill file
|
|
295
|
+
const skillPath = path.join(projectSkillsPath, skillMeta.file);
|
|
296
|
+
try {
|
|
297
|
+
if (fs.existsSync(skillPath)) {
|
|
298
|
+
await fs.promises.unlink(skillPath);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch (e) {
|
|
302
|
+
return {
|
|
303
|
+
success: false,
|
|
304
|
+
error: `Failed to remove skill file: ${e instanceof Error ? e.message : String(e)}`
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
// Update the index
|
|
308
|
+
const newActivated = activatedIndex.activated.filter(id => id !== skillId);
|
|
309
|
+
const newSkills = activatedIndex.skills.filter(s => s.id !== skillId);
|
|
310
|
+
if (newActivated.length === 0) {
|
|
311
|
+
// Remove the index file if no skills remain
|
|
312
|
+
const indexPath = path.join(projectSkillsPath, "index.json");
|
|
313
|
+
try {
|
|
314
|
+
await fs.promises.unlink(indexPath);
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
// ignore
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
// Update the index
|
|
322
|
+
const updatedIndex = {
|
|
323
|
+
...activatedIndex,
|
|
324
|
+
activated: newActivated,
|
|
325
|
+
skills: newSkills
|
|
326
|
+
};
|
|
327
|
+
const indexPath = path.join(projectSkillsPath, "index.json");
|
|
328
|
+
await fs.promises.writeFile(indexPath, JSON.stringify(updatedIndex, null, 2), "utf-8");
|
|
329
|
+
}
|
|
330
|
+
return { success: true };
|
|
331
|
+
}
|
|
332
|
+
// ============================================================
|
|
333
|
+
// Ensure Skills (for setup/update)
|
|
334
|
+
// ============================================================
|
|
335
|
+
/**
|
|
336
|
+
* Ensure skills are installed and healthy
|
|
337
|
+
* Returns the bundle path if successful
|
|
338
|
+
*/
|
|
339
|
+
export function ensureSkills() {
|
|
340
|
+
const health = getSkillsHealth();
|
|
341
|
+
// For prebundled skills, we can't "repair" - they're part of the package
|
|
342
|
+
// If they're corrupted, the user needs to reinstall the package
|
|
343
|
+
return {
|
|
344
|
+
path: health.bundlePath,
|
|
345
|
+
health
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Get skill content by ID
|
|
350
|
+
*/
|
|
351
|
+
export function getSkillContent(skillId) {
|
|
352
|
+
const bundlePath = getSkillsBundlePath();
|
|
353
|
+
if (!bundlePath)
|
|
354
|
+
return null;
|
|
355
|
+
const skill = getSkillById(skillId);
|
|
356
|
+
if (!skill)
|
|
357
|
+
return null;
|
|
358
|
+
const filePath = path.join(bundlePath, skill.file);
|
|
359
|
+
try {
|
|
360
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides gate enforcement for work orders before execution.
|
|
5
5
|
* All work orders must pass through the gate before any work can proceed.
|
|
6
|
+
*
|
|
7
|
+
* All subprocess invocations go through cli_invoker for centralized management.
|
|
6
8
|
*/
|
|
7
|
-
import {
|
|
9
|
+
import { invokeSystem } from "../runtime/cli_invoker.js";
|
|
8
10
|
import { WorkOrderV1Schema } from "../generated/work_order_v1.js";
|
|
9
11
|
import { GateResultV1Schema } from "../generated/gate_result_v1.js";
|
|
10
12
|
/**
|
|
@@ -30,76 +32,56 @@ export async function runSystemDesignGate(workOrder, options) {
|
|
|
30
32
|
pushBool("fail-fast", options.failFast);
|
|
31
33
|
pushBool("semgrep", options.semgrep);
|
|
32
34
|
pushBool("runtime", options.runtime);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
env: { ...process.env, PYTHONIOENCODING: "utf-8" },
|
|
49
|
-
});
|
|
50
|
-
let stdout = "";
|
|
51
|
-
let stderr = "";
|
|
52
|
-
child.stdout?.on("data", (data) => {
|
|
53
|
-
stdout += data.toString();
|
|
54
|
-
});
|
|
55
|
-
child.stderr?.on("data", (data) => {
|
|
56
|
-
stderr += data.toString();
|
|
57
|
-
});
|
|
58
|
-
child.on("close", (code) => {
|
|
59
|
-
// Exit code 0 = ALLOW, 2 = BLOCK, other = error
|
|
60
|
-
if (code === null) {
|
|
61
|
-
resolve({
|
|
62
|
-
success: false,
|
|
63
|
-
error: "Gate check timed out",
|
|
64
|
-
});
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
if (code !== 0 && code !== 2) {
|
|
68
|
-
resolve({
|
|
69
|
-
success: false,
|
|
70
|
-
error: `Gate check failed with code ${code}: ${stderr || stdout}`,
|
|
71
|
-
});
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
try {
|
|
75
|
-
const result = JSON.parse(stdout.trim());
|
|
76
|
-
const validated = GateResultV1Schema.safeParse(result);
|
|
77
|
-
if (!validated.success) {
|
|
78
|
-
resolve({
|
|
79
|
-
success: false,
|
|
80
|
-
error: `Invalid gate result: ${validated.error.message}`,
|
|
81
|
-
});
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
resolve({
|
|
85
|
-
success: true,
|
|
86
|
-
result: validated.data,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
catch (e) {
|
|
90
|
-
resolve({
|
|
91
|
-
success: false,
|
|
92
|
-
error: `Failed to parse gate result: ${e instanceof Error ? e.message : String(e)}`,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
child.on("error", (err) => {
|
|
97
|
-
resolve({
|
|
98
|
-
success: false,
|
|
99
|
-
error: `Gate check process error: ${err.message}`,
|
|
100
|
-
});
|
|
101
|
-
});
|
|
35
|
+
const args = [
|
|
36
|
+
"-m",
|
|
37
|
+
"vibecoding_helper",
|
|
38
|
+
"--repo-root",
|
|
39
|
+
repoRoot,
|
|
40
|
+
"gate-check",
|
|
41
|
+
...overrideFlags,
|
|
42
|
+
"--work-order",
|
|
43
|
+
workOrderJson,
|
|
44
|
+
"--format",
|
|
45
|
+
"json",
|
|
46
|
+
];
|
|
47
|
+
const result = await invokeSystem(pythonExe, args, repoRoot, {
|
|
48
|
+
env: { PYTHONIOENCODING: "utf-8" },
|
|
49
|
+
timeoutMs,
|
|
102
50
|
});
|
|
51
|
+
// Exit code 124 = timeout
|
|
52
|
+
if (result.exitCode === 124) {
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
error: "Gate check timed out",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// Exit code 0 = ALLOW, 2 = BLOCK, other = error
|
|
59
|
+
if (result.exitCode !== 0 && result.exitCode !== 2) {
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
error: `Gate check failed with code ${result.exitCode}: ${result.stderr || result.stdout}`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const parsed = JSON.parse(result.stdout.trim());
|
|
67
|
+
const validated = GateResultV1Schema.safeParse(parsed);
|
|
68
|
+
if (!validated.success) {
|
|
69
|
+
return {
|
|
70
|
+
success: false,
|
|
71
|
+
error: `Invalid gate result: ${validated.error.message}`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
success: true,
|
|
76
|
+
result: validated.data,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
error: `Failed to parse gate result: ${e instanceof Error ? e.message : String(e)}`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
103
85
|
}
|
|
104
86
|
/**
|
|
105
87
|
* Execute a work order only if it passes the gate
|
package/build/dx/activity.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// adapters/mcp-ts/src/dx/activity.ts
|
|
2
2
|
// Activity/DX helpers: branded header, status icons, optional desktop notifications
|
|
3
|
+
import { getWaiterMapping, renderVerdictFromTemplateId, resolveLocaleFromData } from "../tools/vibe_pm/waiter_mapping.js";
|
|
3
4
|
function boolEnv(name, defaultValue) {
|
|
4
5
|
const raw = (process.env[name] ?? "").trim().toLowerCase();
|
|
5
6
|
if (!raw)
|
|
@@ -91,14 +92,35 @@ function verdictIcon(data, outcome) {
|
|
|
91
92
|
}
|
|
92
93
|
return "✅";
|
|
93
94
|
}
|
|
95
|
+
function verdictLabel(data, outcome) {
|
|
96
|
+
if (outcome === "error")
|
|
97
|
+
return "";
|
|
98
|
+
if (!data || typeof data !== "object")
|
|
99
|
+
return "";
|
|
100
|
+
const obj = data;
|
|
101
|
+
const templateId = typeof obj.message_template_id === "string" ? String(obj.message_template_id) : "";
|
|
102
|
+
if (!templateId)
|
|
103
|
+
return "";
|
|
104
|
+
try {
|
|
105
|
+
const mapping = getWaiterMapping();
|
|
106
|
+
const locale = resolveLocaleFromData(data, mapping);
|
|
107
|
+
const label = renderVerdictFromTemplateId(templateId, locale, mapping);
|
|
108
|
+
return label ? label : "";
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
}
|
|
94
114
|
export function renderActivityHeader(toolName, data, outcome = "success", durationMs) {
|
|
95
115
|
const badge = toolBadge(toolName, data);
|
|
96
116
|
const status = verdictIcon(data, outcome);
|
|
117
|
+
const label = verdictLabel(data, outcome);
|
|
97
118
|
const duration = typeof durationMs === "number" && durationMs >= 0 ? ` · ${Math.round(durationMs)}ms` : "";
|
|
119
|
+
const verdict = label ? ` · ${label}` : "";
|
|
98
120
|
return [
|
|
99
121
|
"╭──────────────────────────────╮",
|
|
100
122
|
"│ VibePM ⚡️ Active │",
|
|
101
|
-
`│ ${status} ${badge.icon} ${badge.label}${duration}`,
|
|
123
|
+
`│ ${status} ${badge.icon} ${badge.label}${verdict}${duration}`,
|
|
102
124
|
"╰──────────────────────────────╯"
|
|
103
125
|
].join("\n");
|
|
104
126
|
}
|
|
@@ -107,9 +129,10 @@ export function decorateToolOutputText(toolName, jsonText, data, outcome = "succ
|
|
|
107
129
|
return [{ type: "text", text: jsonText }];
|
|
108
130
|
}
|
|
109
131
|
const header = renderActivityHeader(toolName, data, outcome, durationMs);
|
|
132
|
+
// Combine header and JSON into single block to ensure both are displayed
|
|
133
|
+
// (some MCP clients only show the first content block on error)
|
|
110
134
|
return [
|
|
111
|
-
{ type: "text", text: header }
|
|
112
|
-
{ type: "text", text: jsonText }
|
|
135
|
+
{ type: "text", text: header + "\n" + jsonText }
|
|
113
136
|
];
|
|
114
137
|
}
|
|
115
138
|
export async function notifyDesktopBestEffort(args) {
|