@vibecodetown/mcp-server 2.2.0 → 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/index.js +0 -2
- 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/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 -3
- package/build/local-mode/paths.js +1 -0
- package/build/local-mode/setup.js +21 -1
- package/build/path-utils.js +68 -0
- package/build/runtime/cli_invoker.js +1 -1
- 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 +27 -3
- 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 +78 -40
- package/build/tools/vibe_pm/get_decision.js +2 -2
- package/build/tools/vibe_pm/index.js +128 -42
- 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/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/python_error.js +115 -0
- package/build/tools/vibe_pm/run_app.js +169 -43
- package/build/tools/vibe_pm/run_app_podman.js +64 -2
- 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 +9 -2
- 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/vibe-cli.js +245 -7
- 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
|
+
}
|
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) {
|
package/build/engine.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// adapters/mcp-ts/src/engine.ts
|
|
2
2
|
// Engine execution using cached binaries (no PATH dependency)
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
3
6
|
import { ensureEngines } from "./bootstrap/installer.js";
|
|
4
7
|
import { runCmd } from "./cli.js";
|
|
5
8
|
import { TieredCache, createCacheKey } from "./cache/index.js";
|
|
@@ -104,3 +107,151 @@ export function invalidateEngineCache(pattern) {
|
|
|
104
107
|
export function getEngineCacheStats() {
|
|
105
108
|
return engineResultCache.stats();
|
|
106
109
|
}
|
|
110
|
+
// ============================================================
|
|
111
|
+
// Python CLI Support (vibecoding_helper package)
|
|
112
|
+
// ============================================================
|
|
113
|
+
/**
|
|
114
|
+
* Python CLI commands that are safe to cache (read-only operations)
|
|
115
|
+
*/
|
|
116
|
+
const PYTHON_CACHEABLE_COMMANDS = new Set([
|
|
117
|
+
"memory-status",
|
|
118
|
+
"kce-status",
|
|
119
|
+
"entity-gate",
|
|
120
|
+
"show-ask-queue",
|
|
121
|
+
]);
|
|
122
|
+
function isDebugMode() {
|
|
123
|
+
const v = (process.env.VIBECODE_DEBUG ?? "").trim().toLowerCase();
|
|
124
|
+
return v === "1" || v === "true" || v === "yes" || v === "on";
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Find repo root for Python import.
|
|
128
|
+
*
|
|
129
|
+
* Priority order:
|
|
130
|
+
* 1. VIBECODE_PYTHONPATH environment variable (explicit override)
|
|
131
|
+
* 2. MCP server directory (fallback for when MCP runs from user project)
|
|
132
|
+
* 3. startDir upwards search (heuristic: nearest ancestor with pyproject.toml or vibecoding_helper/)
|
|
133
|
+
*
|
|
134
|
+
* @param startDir - Directory to start searching from
|
|
135
|
+
* @returns Repo root path or null if not found
|
|
136
|
+
*/
|
|
137
|
+
export function findPythonRepoRoot(startDir) {
|
|
138
|
+
// Priority 1: VIBECODE_PYTHONPATH explicit override
|
|
139
|
+
const override = process.env.VIBECODE_PYTHONPATH?.trim();
|
|
140
|
+
if (override && existsSync(override)) {
|
|
141
|
+
return override;
|
|
142
|
+
}
|
|
143
|
+
// Priority 2: MCP server directory fallback
|
|
144
|
+
// When MCP runs from user project, we need to find the package from MCP installation
|
|
145
|
+
try {
|
|
146
|
+
const mcpDir = dirname(fileURLToPath(import.meta.url));
|
|
147
|
+
// mcpDir is typically: .../adapters/mcp-ts/build/
|
|
148
|
+
// repo root is: .../adapters/mcp-ts/build/../../../.. = repo root
|
|
149
|
+
const repoFromMcp = resolve(mcpDir, "..", "..", "..", "..");
|
|
150
|
+
const pkgFromMcp = join(repoFromMcp, "vibecoding_helper");
|
|
151
|
+
if (existsSync(pkgFromMcp)) {
|
|
152
|
+
return repoFromMcp;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// import.meta.url failed, continue to next priority
|
|
157
|
+
}
|
|
158
|
+
// Priority 3: startDir upwards search (original heuristic)
|
|
159
|
+
let current = resolve(startDir);
|
|
160
|
+
// Walk up until filesystem root
|
|
161
|
+
while (true) {
|
|
162
|
+
const pyproject = join(current, "pyproject.toml");
|
|
163
|
+
const pkgDir = join(current, "vibecoding_helper");
|
|
164
|
+
// Check for pyproject.toml or vibecoding_helper/ directory
|
|
165
|
+
if (existsSync(pyproject) || existsSync(pkgDir)) {
|
|
166
|
+
return current;
|
|
167
|
+
}
|
|
168
|
+
const parent = dirname(current);
|
|
169
|
+
if (parent === current)
|
|
170
|
+
break; // Reached filesystem root
|
|
171
|
+
current = parent;
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Build env for Python CLI so that 'python -m vibecoding_helper ...' can import
|
|
177
|
+
* even when MCP client ignores .mcp.json env (e.g., Claude Code).
|
|
178
|
+
*
|
|
179
|
+
* Priority:
|
|
180
|
+
* 1. VIBECODE_PYTHONPATH (explicit override)
|
|
181
|
+
* 2. Auto-detected repo root
|
|
182
|
+
* 3. Existing PYTHONPATH
|
|
183
|
+
*
|
|
184
|
+
* @param baseEnv - Base environment variables
|
|
185
|
+
* @param cwd - Current working directory for repo root detection
|
|
186
|
+
* @returns Environment with PYTHONPATH properly set
|
|
187
|
+
*/
|
|
188
|
+
export function buildPythonCliEnv(baseEnv, cwd) {
|
|
189
|
+
const env = { ...baseEnv };
|
|
190
|
+
const delim = process.platform === "win32" ? ";" : ":";
|
|
191
|
+
// Priority 1: Explicit override via VIBECODE_PYTHONPATH
|
|
192
|
+
const override = env.VIBECODE_PYTHONPATH?.trim();
|
|
193
|
+
// Priority 2: Auto-detect repo root
|
|
194
|
+
const repoRoot = override || findPythonRepoRoot(cwd);
|
|
195
|
+
if (!repoRoot)
|
|
196
|
+
return env;
|
|
197
|
+
// Parse existing PYTHONPATH
|
|
198
|
+
const existing = env.PYTHONPATH ?? "";
|
|
199
|
+
const parts = existing.split(delim).filter(Boolean);
|
|
200
|
+
// Avoid duplicate insertion
|
|
201
|
+
if (!parts.includes(repoRoot)) {
|
|
202
|
+
env.PYTHONPATH = [repoRoot, ...parts].join(delim);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
env.PYTHONPATH = parts.join(delim);
|
|
206
|
+
}
|
|
207
|
+
return env;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Run Python vibecoding_helper CLI command
|
|
211
|
+
* Uses `python -m vibecoding_helper` for cross-platform compatibility
|
|
212
|
+
*
|
|
213
|
+
* Automatically injects PYTHONPATH by detecting the package root,
|
|
214
|
+
* making it work without .mcp.json env configuration.
|
|
215
|
+
*/
|
|
216
|
+
export async function runPythonCli(args, opts) {
|
|
217
|
+
const pythonCmd = process.platform === "win32" ? "python" : "python3";
|
|
218
|
+
const fullArgs = ["-m", "vibecoding_helper", ...args];
|
|
219
|
+
const cwd = process.cwd();
|
|
220
|
+
// Auto-inject PYTHONPATH using buildPythonCliEnv
|
|
221
|
+
const env = buildPythonCliEnv(process.env, cwd);
|
|
222
|
+
if (isDebugMode()) {
|
|
223
|
+
const repoRoot = findPythonRepoRoot(cwd);
|
|
224
|
+
console.error(`[DEBUG runPythonCli] cmd=${pythonCmd} args=${JSON.stringify(fullArgs)}`);
|
|
225
|
+
console.error(`[DEBUG runPythonCli] PYTHONPATH=${env.PYTHONPATH ?? "(not set)"}`);
|
|
226
|
+
console.error(`[DEBUG runPythonCli] detected_root=${repoRoot ?? "(not found)"}`);
|
|
227
|
+
console.error(`[DEBUG runPythonCli] cwd=${cwd}`);
|
|
228
|
+
}
|
|
229
|
+
const result = await runCmd(pythonCmd, fullArgs, {
|
|
230
|
+
timeoutMs: opts?.timeoutMs ?? 120_000,
|
|
231
|
+
env,
|
|
232
|
+
});
|
|
233
|
+
if (isDebugMode()) {
|
|
234
|
+
console.error(`[DEBUG runPythonCli] code=${result.code} stdout=${result.stdout.slice(0, 200)} stderr=${result.stderr.slice(0, 200)}`);
|
|
235
|
+
}
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Run Python CLI command with optional caching support
|
|
240
|
+
*/
|
|
241
|
+
export async function runPythonCliWithCache(args, opts) {
|
|
242
|
+
const subCommand = extractSubcommand(args);
|
|
243
|
+
const isCacheable = PYTHON_CACHEABLE_COMMANDS.has(subCommand) && !opts?.skipCache;
|
|
244
|
+
if (isCacheable) {
|
|
245
|
+
const cacheKey = createCacheKey("python-cli", ...args);
|
|
246
|
+
const cachedResult = engineResultCache.get(cacheKey);
|
|
247
|
+
if (cachedResult) {
|
|
248
|
+
return cachedResult;
|
|
249
|
+
}
|
|
250
|
+
const result = await runPythonCli(args, opts);
|
|
251
|
+
if (result.code === 0) {
|
|
252
|
+
engineResultCache.set(cacheKey, result);
|
|
253
|
+
}
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
return await runPythonCli(args, opts);
|
|
257
|
+
}
|
package/build/errors.js
CHANGED
|
@@ -169,3 +169,110 @@ export function doNotTouchError(path, pattern) {
|
|
|
169
169
|
recovery: "This path is protected and cannot be modified. Choose a different target.",
|
|
170
170
|
});
|
|
171
171
|
}
|
|
172
|
+
// ============================================================
|
|
173
|
+
// Error Detection Patterns (P1-4: Normalized string-based error detection)
|
|
174
|
+
// ============================================================
|
|
175
|
+
/**
|
|
176
|
+
* Centralized error string patterns for consistent detection across the codebase.
|
|
177
|
+
* Use these constants instead of hardcoded strings for maintainability.
|
|
178
|
+
*/
|
|
179
|
+
export const ErrorPatterns = {
|
|
180
|
+
// Bootstrap/download errors
|
|
181
|
+
DOWNLOAD_TIMEOUT: ["download_timeout", "AbortError"],
|
|
182
|
+
DOWNLOAD_404: ["404", "no_matching_asset", "download_failed:404"],
|
|
183
|
+
DOWNLOAD_FAILED: ["download_failed", "fetch"],
|
|
184
|
+
SHA_MISMATCH: ["sha_mismatch"],
|
|
185
|
+
SHA_MISSING: ["sha_missing_for_asset"],
|
|
186
|
+
BIN_NOT_FOUND: ["bin_not_found_after_extract"],
|
|
187
|
+
// Git errors
|
|
188
|
+
GIT_CONFLICT: ["conflict", "CONFLICT"],
|
|
189
|
+
// OPA/Policy errors
|
|
190
|
+
OPA_EVAL_FAILED: ["eval_failed", "opa_eval_failed", "opa exception", "opa_exception", "eval failed"],
|
|
191
|
+
OPA_MISSING: ["missing"],
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* Check if a message matches any of the given patterns.
|
|
195
|
+
* Case-insensitive matching for OPA patterns, case-sensitive for others.
|
|
196
|
+
*/
|
|
197
|
+
export function matchesErrorPattern(message, patterns, options = {}) {
|
|
198
|
+
const normalizedMessage = options.caseInsensitive ? message.toLowerCase() : message;
|
|
199
|
+
return patterns.some((pattern) => {
|
|
200
|
+
const normalizedPattern = options.caseInsensitive ? pattern.toLowerCase() : pattern;
|
|
201
|
+
return normalizedMessage.includes(normalizedPattern);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Check if message indicates a download timeout error.
|
|
206
|
+
*/
|
|
207
|
+
export function isDownloadTimeoutError(message) {
|
|
208
|
+
return matchesErrorPattern(message, ErrorPatterns.DOWNLOAD_TIMEOUT);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Check if message indicates a 404/not found error.
|
|
212
|
+
*/
|
|
213
|
+
export function isDownload404Error(message) {
|
|
214
|
+
return matchesErrorPattern(message, ErrorPatterns.DOWNLOAD_404);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Check if message indicates a general download failure.
|
|
218
|
+
*/
|
|
219
|
+
export function isDownloadFailedError(message) {
|
|
220
|
+
return matchesErrorPattern(message, ErrorPatterns.DOWNLOAD_FAILED);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Check if message indicates a git conflict.
|
|
224
|
+
*/
|
|
225
|
+
export function isGitConflictError(message) {
|
|
226
|
+
return matchesErrorPattern(message, ErrorPatterns.GIT_CONFLICT);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Check if message indicates an OPA evaluation failure.
|
|
230
|
+
* Always case-insensitive for OPA patterns.
|
|
231
|
+
*/
|
|
232
|
+
export function isOpaEvalFailedError(message) {
|
|
233
|
+
return matchesErrorPattern(message, ErrorPatterns.OPA_EVAL_FAILED, { caseInsensitive: true });
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Check if message indicates OPA is missing.
|
|
237
|
+
*/
|
|
238
|
+
export function isOpaMissingError(message) {
|
|
239
|
+
return matchesErrorPattern(message, ErrorPatterns.OPA_MISSING, { caseInsensitive: true });
|
|
240
|
+
}
|
|
241
|
+
// ============================================================
|
|
242
|
+
// P2-23: Error Message Formatting Utilities
|
|
243
|
+
// ============================================================
|
|
244
|
+
/**
|
|
245
|
+
* Format CLI command failure error message.
|
|
246
|
+
* @param command - The command that failed (e.g., "kce-status", "zoekt-evidence")
|
|
247
|
+
* @param exitCode - Exit code from the process
|
|
248
|
+
* @param stderr - Standard error output
|
|
249
|
+
* @param stdout - Standard output (used as fallback)
|
|
250
|
+
*/
|
|
251
|
+
export function formatCliError(command, exitCode, stderr, stdout) {
|
|
252
|
+
const detail = stderr || stdout || `exit_code=${exitCode ?? "unknown"}`;
|
|
253
|
+
return `${command} failed: ${detail}`;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Format JSON parsing error message.
|
|
257
|
+
* @param context - Context where parsing failed (e.g., "kce-status", "config")
|
|
258
|
+
* @param error - The parsing error
|
|
259
|
+
*/
|
|
260
|
+
export function formatJsonParseError(context, error) {
|
|
261
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
262
|
+
return `${context} invalid_json: ${msg}`;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Format schema validation error message.
|
|
266
|
+
* @param context - Context where validation failed
|
|
267
|
+
*/
|
|
268
|
+
export function formatSchemaError(context) {
|
|
269
|
+
return `${context} schema mismatch`;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Format user-facing Korean error message.
|
|
273
|
+
* @param action - What action failed (e.g., "프로젝트 초기화", "작업 지시서 생성")
|
|
274
|
+
* @param detail - Additional detail (optional)
|
|
275
|
+
*/
|
|
276
|
+
export function formatKoreanError(action, detail) {
|
|
277
|
+
return `${action} 실패${detail ? `: ${detail}` : ""}`;
|
|
278
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const BridgeBuildSeedInputSchema = z.object({ "project_id": z.string().min(1), "run_id": z.string().min(1).optional(), "raw_user_intent": z.string().min(1).max(4000).optional(), "goal": z.string().min(1).max(2000).optional(), "user_story": z.string().min(1).max(2000).optional(), "acceptance": z.array(z.string().min(1).max(300)).min(3).max(12).optional(), "constraints": z.array(z.string().min(1).max(200)).max(12).optional(), "non_goals": z.array(z.string().min(1).max(200)).max(8).optional(), "scope_seed": z.object({ "include": z.array(z.string().min(1).max(300)).min(1).max(20), "exclude": z.array(z.string().min(1).max(300)).max(20), "do_not_touch": z.array(z.string().min(1).max(300)).max(20).optional() }).strict().optional(), "preferred_stack_hint": z.string().max(200).optional(), "risk_notes": z.array(z.string().min(1).max(200)).max(8).optional(), "overwrite": z.boolean().default(true), "write_files": z.boolean().default(true) }).strict().describe("SSOT schema for vibe_pm.bridge_build_seed MCP tool input");
|