@vibecodetown/mcp-server 2.1.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/LICENSE +21 -0
- package/README.md +269 -0
- package/build/auth/gate.js +225 -0
- package/build/auth/index.js +55 -0
- package/build/auth/public_key.js +27 -0
- package/build/auth/token_cache.js +122 -0
- package/build/auth/token_verifier.js +103 -0
- package/build/bootstrap/doctor.js +115 -0
- package/build/bootstrap/installer.js +673 -0
- package/build/bootstrap/lock.js +37 -0
- package/build/bootstrap/platform.js +26 -0
- package/build/bootstrap/registry.js +37 -0
- package/build/cache/index.js +147 -0
- package/build/cli.js +101 -0
- package/build/contracts.js +22 -0
- package/build/control_plane/gate.js +161 -0
- package/build/control_plane/index.js +6 -0
- package/build/dx/activity.js +139 -0
- package/build/engine.js +106 -0
- package/build/errors.js +171 -0
- package/build/generated/activate_input.js +2 -0
- package/build/generated/activate_output.js +57 -0
- package/build/generated/advisory_review_input.js +2 -0
- package/build/generated/advisory_review_output.js +35 -0
- package/build/generated/auth_token_file.js +2 -0
- package/build/generated/briefing_input.js +2 -0
- package/build/generated/briefing_output.js +2 -0
- package/build/generated/clinic_bridge_file.js +13 -0
- package/build/generated/contracts_bundle_info.js +5 -0
- package/build/generated/create_work_order_input.js +2 -0
- package/build/generated/create_work_order_output.js +2 -0
- package/build/generated/current_work_order_file.js +2 -0
- package/build/generated/doctor_input.js +2 -0
- package/build/generated/doctor_output.js +24 -0
- package/build/generated/execution_result.js +2 -0
- package/build/generated/execution_task.js +2 -0
- package/build/generated/export_output_input.js +2 -0
- package/build/generated/export_output_output.js +2 -0
- package/build/generated/finalize_work_input.js +2 -0
- package/build/generated/finalize_work_output.js +2 -0
- package/build/generated/gate_input.js +2 -0
- package/build/generated/gate_output.js +2 -0
- package/build/generated/gate_result_v1.js +2 -0
- package/build/generated/get_decision_input.js +2 -0
- package/build/generated/get_decision_output.js +13 -0
- package/build/generated/handoff_to_clinic.js +2 -0
- package/build/generated/index.js +75 -0
- package/build/generated/inspect_code_input.js +2 -0
- package/build/generated/inspect_code_output.js +13 -0
- package/build/generated/memory_retrieve_output.js +2 -0
- package/build/generated/memory_state_file.js +2 -0
- package/build/generated/memory_status_input.js +2 -0
- package/build/generated/memory_status_output.js +13 -0
- package/build/generated/memory_sync_input.js +2 -0
- package/build/generated/memory_sync_output.js +13 -0
- package/build/generated/plugin_result.js +2 -0
- package/build/generated/react_perf_check_patterns_input.js +2 -0
- package/build/generated/react_perf_check_patterns_output.js +2 -0
- package/build/generated/react_perf_generate_report_input.js +2 -0
- package/build/generated/react_perf_generate_report_output.js +2 -0
- package/build/generated/repair_plan_input.js +2 -0
- package/build/generated/repair_plan_output.js +2 -0
- package/build/generated/run_app_input.js +2 -0
- package/build/generated/run_app_output.js +2 -0
- package/build/generated/run_state_file.js +13 -0
- package/build/generated/scaffold_input.js +2 -0
- package/build/generated/scaffold_output.js +2 -0
- package/build/generated/search_oss_input.js +2 -0
- package/build/generated/search_oss_output.js +2 -0
- package/build/generated/selection_validation_result.js +2 -0
- package/build/generated/signal_agent_input.js +2 -0
- package/build/generated/spec_high_ask_queue_items_file.js +2 -0
- package/build/generated/spec_high_clinic_bridge_output.js +2 -0
- package/build/generated/spec_high_decision_draft_output.js +2 -0
- package/build/generated/spec_high_validate_output.js +2 -0
- package/build/generated/status_input.js +2 -0
- package/build/generated/status_output.js +2 -0
- package/build/generated/submit_decision_input.js +2 -0
- package/build/generated/submit_decision_output.js +2 -0
- package/build/generated/tool_error_output.js +2 -0
- package/build/generated/undo_last_task_input.js +2 -0
- package/build/generated/undo_last_task_output.js +2 -0
- package/build/generated/update_input.js +2 -0
- package/build/generated/update_output.js +2 -0
- package/build/generated/vibe_pm_inspection_result.js +2 -0
- package/build/generated/vibe_pm_report_markdown.js +2 -0
- package/build/generated/vibe_pm_verdict.js +2 -0
- package/build/generated/vibe_repo_config.js +2 -0
- package/build/generated/vibecoding_helper_answer_output.js +2 -0
- package/build/generated/vibecoding_helper_one_loop_selection_output.js +2 -0
- package/build/generated/vibecoding_helper_show_ask_queue_output.js +2 -0
- package/build/generated/work_order_v1.js +2 -0
- package/build/generated/zoekt_evidence_input.js +2 -0
- package/build/generated/zoekt_evidence_output.js +2 -0
- package/build/index.js +111 -0
- package/build/legacy_alias.js +65 -0
- package/build/local-mode/bash.js +61 -0
- package/build/local-mode/config.js +171 -0
- package/build/local-mode/git.js +33 -0
- package/build/local-mode/init.js +110 -0
- package/build/local-mode/paths.js +24 -0
- package/build/local-mode/templates.js +856 -0
- package/build/local-mode/work-order.js +41 -0
- package/build/resources/index.js +246 -0
- package/build/security/input-validator.js +119 -0
- package/build/security/path-policy.js +289 -0
- package/build/security/sandbox.js +228 -0
- package/build/tools/react_perf/check_patterns.js +172 -0
- package/build/tools/react_perf/generate_report.js +337 -0
- package/build/tools/react_perf/index.js +119 -0
- package/build/tools/react_perf/rules/advanced.js +325 -0
- package/build/tools/react_perf/rules/async.js +104 -0
- package/build/tools/react_perf/rules/bundle.js +101 -0
- package/build/tools/react_perf/rules/client.js +186 -0
- package/build/tools/react_perf/rules/index.js +74 -0
- package/build/tools/react_perf/rules/js.js +148 -0
- package/build/tools/react_perf/rules/rendering.js +166 -0
- package/build/tools/react_perf/rules/rerender.js +161 -0
- package/build/tools/react_perf/rules/server.js +141 -0
- package/build/tools/react_perf/types.js +127 -0
- package/build/tools/vibe_pm/activate.js +102 -0
- package/build/tools/vibe_pm/advisory_review.js +77 -0
- package/build/tools/vibe_pm/briefing.js +178 -0
- package/build/tools/vibe_pm/context.js +439 -0
- package/build/tools/vibe_pm/create_work_order.js +271 -0
- package/build/tools/vibe_pm/doc_status_gate.js +370 -0
- package/build/tools/vibe_pm/doctor.js +262 -0
- package/build/tools/vibe_pm/entity_gate/preflight.js +78 -0
- package/build/tools/vibe_pm/export_output.js +135 -0
- package/build/tools/vibe_pm/finalize_work.js +393 -0
- package/build/tools/vibe_pm/gate.js +33 -0
- package/build/tools/vibe_pm/get_decision.js +281 -0
- package/build/tools/vibe_pm/index.js +593 -0
- package/build/tools/vibe_pm/inspect_code.js +828 -0
- package/build/tools/vibe_pm/intent/generator.js +294 -0
- package/build/tools/vibe_pm/intent/index.js +5 -0
- package/build/tools/vibe_pm/intent/prompt_density.js +227 -0
- package/build/tools/vibe_pm/intent/types.js +70 -0
- package/build/tools/vibe_pm/intent/verifier.js +237 -0
- package/build/tools/vibe_pm/kce/doc_usage.js +51 -0
- package/build/tools/vibe_pm/kce/on_finalize.js +11 -0
- package/build/tools/vibe_pm/kce/preflight.js +232 -0
- package/build/tools/vibe_pm/local_memory.js +26 -0
- package/build/tools/vibe_pm/memory_status.js +82 -0
- package/build/tools/vibe_pm/memory_sync.js +134 -0
- package/build/tools/vibe_pm/modules/decision_snapshot.js +29 -0
- package/build/tools/vibe_pm/modules/ensure.js +100 -0
- package/build/tools/vibe_pm/modules/fingerprint.js +30 -0
- package/build/tools/vibe_pm/modules/fix_dependencies.js +394 -0
- package/build/tools/vibe_pm/modules/planning_v1.js +110 -0
- package/build/tools/vibe_pm/modules/repo_context.js +56 -0
- package/build/tools/vibe_pm/modules/research_v1.js +114 -0
- package/build/tools/vibe_pm/modules/skills_v1.js +100 -0
- package/build/tools/vibe_pm/pm_language.js +222 -0
- package/build/tools/vibe_pm/repair_plan.js +199 -0
- package/build/tools/vibe_pm/run_app.js +597 -0
- package/build/tools/vibe_pm/run_app_podman.js +64 -0
- package/build/tools/vibe_pm/scaffold.js +550 -0
- package/build/tools/vibe_pm/search_oss.js +124 -0
- package/build/tools/vibe_pm/status.js +153 -0
- package/build/tools/vibe_pm/submit_decision.js +87 -0
- package/build/tools/vibe_pm/system_design/issue_mapping.js +47 -0
- package/build/tools/vibe_pm/system_design/rulebook.js +112 -0
- package/build/tools/vibe_pm/system_design/semgrep.js +132 -0
- package/build/tools/vibe_pm/types.js +229 -0
- package/build/tools/vibe_pm/undo_last_task.js +163 -0
- package/build/tools/vibe_pm/update.js +146 -0
- package/build/tools/vibe_pm/zoekt_evidence.js +96 -0
- package/build/tools.js +269 -0
- package/build/version-check.js +239 -0
- package/build/vibe-cli.js +631 -0
- package/package.json +76 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/bootstrap/platform.ts
|
|
2
|
+
// Platform detection and executable naming
|
|
3
|
+
/**
|
|
4
|
+
* Detect current platform for asset download
|
|
5
|
+
*/
|
|
6
|
+
export function detectPlatform() {
|
|
7
|
+
const p = process.platform;
|
|
8
|
+
const a = process.arch;
|
|
9
|
+
if (p === "win32")
|
|
10
|
+
return "win_x64"; // win arm64 out of scope for v1
|
|
11
|
+
if (p === "darwin") {
|
|
12
|
+
if (a === "arm64")
|
|
13
|
+
return "darwin_arm64";
|
|
14
|
+
return "darwin_x64";
|
|
15
|
+
}
|
|
16
|
+
// linux
|
|
17
|
+
if (a === "arm64")
|
|
18
|
+
return "linux_arm64";
|
|
19
|
+
return "linux_x64";
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get executable name with platform-specific extension
|
|
23
|
+
*/
|
|
24
|
+
export function exeName(base) {
|
|
25
|
+
return process.platform === "win32" ? `${base}.exe` : base;
|
|
26
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/bootstrap/registry.ts
|
|
2
|
+
// SSOT: Engine versions and release asset naming
|
|
3
|
+
/**
|
|
4
|
+
* Engine Registry (SSOT)
|
|
5
|
+
*
|
|
6
|
+
* VERSION_MATRIX.md์ ๋๊ธฐํ ํ์.
|
|
7
|
+
* ์ฌ๊ธฐ ๋ฒ์ ์ ๋ฐ๊พธ๋ฉด ์ํด๋ฆญ ์ค์น ๋์์ด ๋ฐ๋๋ค.
|
|
8
|
+
*/
|
|
9
|
+
export const ENGINE_SPECS = {
|
|
10
|
+
"spec-high": {
|
|
11
|
+
name: "spec-high",
|
|
12
|
+
version: "1.0.0",
|
|
13
|
+
repo: "yellowhama/vibe-pm",
|
|
14
|
+
assetPrefix: "spec-high",
|
|
15
|
+
shaSumsAsset: "SHA256SUMS.txt"
|
|
16
|
+
},
|
|
17
|
+
"vibecoding-helper": {
|
|
18
|
+
name: "vibecoding-helper",
|
|
19
|
+
version: "1.0.0",
|
|
20
|
+
repo: "yellowhama/vibe-pm",
|
|
21
|
+
assetPrefix: "vibecoding-helper",
|
|
22
|
+
shaSumsAsset: "SHA256SUMS.txt"
|
|
23
|
+
},
|
|
24
|
+
"clinic": {
|
|
25
|
+
name: "clinic",
|
|
26
|
+
version: "2.3.0",
|
|
27
|
+
repo: "yellowhama/vibe-pm",
|
|
28
|
+
assetPrefix: "clinic",
|
|
29
|
+
shaSumsAsset: "SHA256SUMS.txt"
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Asset naming convention:
|
|
34
|
+
* {assetPrefix}_{version}_{platform}.tar.gz
|
|
35
|
+
*
|
|
36
|
+
* Example: spec-high_1.0.0_linux_x64.tar.gz
|
|
37
|
+
*/
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/cache/index.ts
|
|
2
|
+
// TieredCache - TTL + mtime-based cache with LRU eviction
|
|
3
|
+
/**
|
|
4
|
+
* TieredCache - Multi-level cache with TTL and mtime validation
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - TTL-based expiration
|
|
8
|
+
* - Optional mtime validation for file-based caching
|
|
9
|
+
* - LRU eviction when maxEntries exceeded
|
|
10
|
+
* - Pattern-based invalidation
|
|
11
|
+
*/
|
|
12
|
+
export class TieredCache {
|
|
13
|
+
config;
|
|
14
|
+
cache = new Map();
|
|
15
|
+
accessOrder = [];
|
|
16
|
+
hits = 0;
|
|
17
|
+
misses = 0;
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.config = config;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get cached value if valid
|
|
23
|
+
*
|
|
24
|
+
* @param key - Cache key
|
|
25
|
+
* @param mtimeGetter - Optional function to get current mtime for validation
|
|
26
|
+
* @returns Cached value or null if expired/invalid
|
|
27
|
+
*/
|
|
28
|
+
get(key, mtimeGetter) {
|
|
29
|
+
const entry = this.cache.get(key);
|
|
30
|
+
if (!entry) {
|
|
31
|
+
this.misses++;
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
// Check TTL expiration
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
if (now - entry.timestamp > this.config.ttlMs) {
|
|
37
|
+
this.cache.delete(key);
|
|
38
|
+
this.removeFromAccessOrder(key);
|
|
39
|
+
this.misses++;
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
// Check mtime validation if provided
|
|
43
|
+
if (mtimeGetter && entry.mtime !== undefined) {
|
|
44
|
+
const currentMtime = mtimeGetter();
|
|
45
|
+
if (currentMtime !== null && currentMtime !== entry.mtime) {
|
|
46
|
+
this.cache.delete(key);
|
|
47
|
+
this.removeFromAccessOrder(key);
|
|
48
|
+
this.misses++;
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Update access order for LRU
|
|
53
|
+
this.updateAccessOrder(key);
|
|
54
|
+
this.hits++;
|
|
55
|
+
return entry.value;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Set cache value
|
|
59
|
+
*
|
|
60
|
+
* @param key - Cache key
|
|
61
|
+
* @param value - Value to cache
|
|
62
|
+
* @param mtime - Optional mtime for file-based validation
|
|
63
|
+
*/
|
|
64
|
+
set(key, value, mtime) {
|
|
65
|
+
// Evict if at capacity
|
|
66
|
+
if (this.cache.size >= this.config.maxEntries && !this.cache.has(key)) {
|
|
67
|
+
this.evictLRU();
|
|
68
|
+
}
|
|
69
|
+
this.cache.set(key, {
|
|
70
|
+
value,
|
|
71
|
+
timestamp: Date.now(),
|
|
72
|
+
mtime,
|
|
73
|
+
});
|
|
74
|
+
this.updateAccessOrder(key);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Invalidate specific cache entry
|
|
78
|
+
*/
|
|
79
|
+
invalidate(key) {
|
|
80
|
+
this.cache.delete(key);
|
|
81
|
+
this.removeFromAccessOrder(key);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Invalidate all entries matching a pattern
|
|
85
|
+
*/
|
|
86
|
+
invalidatePattern(pattern) {
|
|
87
|
+
const keysToDelete = [];
|
|
88
|
+
for (const key of this.cache.keys()) {
|
|
89
|
+
if (pattern.test(key)) {
|
|
90
|
+
keysToDelete.push(key);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
for (const key of keysToDelete) {
|
|
94
|
+
this.cache.delete(key);
|
|
95
|
+
this.removeFromAccessOrder(key);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Clear all cache entries
|
|
100
|
+
*/
|
|
101
|
+
clear() {
|
|
102
|
+
this.cache.clear();
|
|
103
|
+
this.accessOrder = [];
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get cache statistics
|
|
107
|
+
*/
|
|
108
|
+
stats() {
|
|
109
|
+
return {
|
|
110
|
+
size: this.cache.size,
|
|
111
|
+
maxEntries: this.config.maxEntries,
|
|
112
|
+
name: this.config.name,
|
|
113
|
+
hits: this.hits,
|
|
114
|
+
misses: this.misses,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Check if key exists and is valid
|
|
119
|
+
*/
|
|
120
|
+
has(key, mtimeGetter) {
|
|
121
|
+
return this.get(key, mtimeGetter) !== null;
|
|
122
|
+
}
|
|
123
|
+
updateAccessOrder(key) {
|
|
124
|
+
this.removeFromAccessOrder(key);
|
|
125
|
+
this.accessOrder.push(key);
|
|
126
|
+
}
|
|
127
|
+
removeFromAccessOrder(key) {
|
|
128
|
+
const index = this.accessOrder.indexOf(key);
|
|
129
|
+
if (index !== -1) {
|
|
130
|
+
this.accessOrder.splice(index, 1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
evictLRU() {
|
|
134
|
+
if (this.accessOrder.length === 0)
|
|
135
|
+
return;
|
|
136
|
+
const lruKey = this.accessOrder.shift();
|
|
137
|
+
if (lruKey) {
|
|
138
|
+
this.cache.delete(lruKey);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Create a cache key from multiple parts
|
|
144
|
+
*/
|
|
145
|
+
export function createCacheKey(...parts) {
|
|
146
|
+
return parts.filter((p) => p !== undefined).join("::");
|
|
147
|
+
}
|
package/build/cli.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
/**
|
|
3
|
+
* Environment variable whitelist for subprocess execution
|
|
4
|
+
* Only these variables are passed through to child processes
|
|
5
|
+
*/
|
|
6
|
+
const ENV_WHITELIST = new Set([
|
|
7
|
+
// System essentials
|
|
8
|
+
"PATH",
|
|
9
|
+
"HOME",
|
|
10
|
+
"USER",
|
|
11
|
+
"LANG",
|
|
12
|
+
"LC_ALL",
|
|
13
|
+
"LC_CTYPE",
|
|
14
|
+
"TZ",
|
|
15
|
+
"TERM",
|
|
16
|
+
"SHELL",
|
|
17
|
+
// Vibe-specific
|
|
18
|
+
"VIBECODE_CACHE",
|
|
19
|
+
"VIBECODE_ROOT",
|
|
20
|
+
"VIBECODE_DEBUG",
|
|
21
|
+
"VIBECODE_OFFLINE",
|
|
22
|
+
// Runtime paths
|
|
23
|
+
"NODE_PATH",
|
|
24
|
+
"PYTHONPATH",
|
|
25
|
+
"PYTHONIOENCODING",
|
|
26
|
+
// Build tools
|
|
27
|
+
"CC",
|
|
28
|
+
"CXX",
|
|
29
|
+
"MAKE",
|
|
30
|
+
]);
|
|
31
|
+
/**
|
|
32
|
+
* Sanitize environment variables by filtering through whitelist
|
|
33
|
+
*
|
|
34
|
+
* @param additionalEnv - Optional additional env vars to include
|
|
35
|
+
* @returns Filtered environment object
|
|
36
|
+
*/
|
|
37
|
+
export function sanitizeEnv(additionalEnv) {
|
|
38
|
+
const result = {};
|
|
39
|
+
// Filter process.env through whitelist
|
|
40
|
+
for (const key of ENV_WHITELIST) {
|
|
41
|
+
if (process.env[key] !== undefined) {
|
|
42
|
+
result[key] = process.env[key];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Add any additional env vars (also filtered through whitelist for security)
|
|
46
|
+
if (additionalEnv) {
|
|
47
|
+
for (const [key, value] of Object.entries(additionalEnv)) {
|
|
48
|
+
if (value !== undefined && ENV_WHITELIST.has(key)) {
|
|
49
|
+
result[key] = value;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
export async function runCmd(cmd, args, opts) {
|
|
56
|
+
const timeoutMs = opts?.timeoutMs ?? 120_000;
|
|
57
|
+
return await new Promise((resolve) => {
|
|
58
|
+
let resolved = false;
|
|
59
|
+
const p = spawn(cmd, args, {
|
|
60
|
+
cwd: opts?.cwd,
|
|
61
|
+
env: sanitizeEnv(opts?.env),
|
|
62
|
+
shell: false,
|
|
63
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
64
|
+
});
|
|
65
|
+
let stdout = "";
|
|
66
|
+
let stderr = "";
|
|
67
|
+
const finish = (code, out, err) => {
|
|
68
|
+
if (resolved)
|
|
69
|
+
return;
|
|
70
|
+
resolved = true;
|
|
71
|
+
resolve({ code, stdout: out, stderr: err });
|
|
72
|
+
};
|
|
73
|
+
const killTimer = setTimeout(() => {
|
|
74
|
+
try {
|
|
75
|
+
p.kill("SIGKILL");
|
|
76
|
+
}
|
|
77
|
+
catch { }
|
|
78
|
+
finish(124, stdout, stderr + "\n[TIMEOUT]");
|
|
79
|
+
}, timeoutMs);
|
|
80
|
+
p.stdout.on("data", (d) => (stdout += d.toString("utf-8")));
|
|
81
|
+
p.stderr.on("data", (d) => (stderr += d.toString("utf-8")));
|
|
82
|
+
p.on("error", (e) => {
|
|
83
|
+
clearTimeout(killTimer);
|
|
84
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
85
|
+
finish(127, stdout, stderr + `\n[SPAWN_ERROR] ${msg}`);
|
|
86
|
+
});
|
|
87
|
+
p.on("close", (code) => {
|
|
88
|
+
clearTimeout(killTimer);
|
|
89
|
+
finish(code ?? 1, stdout, stderr);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export function safeJsonParse(raw) {
|
|
94
|
+
try {
|
|
95
|
+
return { ok: true, value: JSON.parse(raw) };
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
99
|
+
return { ok: false, error: msg };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// contracts.ts
|
|
2
|
+
// Central contract types for MCP adapter
|
|
3
|
+
// SelectionValidationResult is generated from JSON Schema SSOT
|
|
4
|
+
import { SelectionValidationResultSchema } from "./generated/selection_validation_result.js";
|
|
5
|
+
import { ToolErrorOutputSchema } from "./generated/tool_error_output.js";
|
|
6
|
+
// ============================================================
|
|
7
|
+
// Generated Types (from JSON Schema SSOT)
|
|
8
|
+
// ============================================================
|
|
9
|
+
export { SelectionValidationResultSchema };
|
|
10
|
+
export { ToolErrorOutputSchema };
|
|
11
|
+
export const SignalLevelSchema = SelectionValidationResultSchema.shape.signal_level;
|
|
12
|
+
export const DetailsSchema = SelectionValidationResultSchema.shape.details;
|
|
13
|
+
export const NextActionSchema = SelectionValidationResultSchema.shape.next_action;
|
|
14
|
+
// Backwards-compat alias (older docs / consumers)
|
|
15
|
+
export const NextActionObjectSchema = NextActionSchema;
|
|
16
|
+
export const PathsSchema = SelectionValidationResultSchema.shape.paths;
|
|
17
|
+
export function parseSelectionValidationResult(input) {
|
|
18
|
+
return SelectionValidationResultSchema.parse(input);
|
|
19
|
+
}
|
|
20
|
+
export function safeParseSelectionValidationResult(input) {
|
|
21
|
+
return SelectionValidationResultSchema.safeParse(input);
|
|
22
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System Design Gate - Control Plane for Vibe PM
|
|
3
|
+
*
|
|
4
|
+
* Provides gate enforcement for work orders before execution.
|
|
5
|
+
* All work orders must pass through the gate before any work can proceed.
|
|
6
|
+
*/
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
import { WorkOrderV1Schema } from "../generated/work_order_v1.js";
|
|
9
|
+
import { GateResultV1Schema } from "../generated/gate_result_v1.js";
|
|
10
|
+
/**
|
|
11
|
+
* Run the system design gate check on a work order
|
|
12
|
+
*/
|
|
13
|
+
export async function runSystemDesignGate(workOrder, options) {
|
|
14
|
+
const { repoRoot, pythonExe = "python3", timeoutMs = 30000 } = options;
|
|
15
|
+
// Validate work order first
|
|
16
|
+
const parseResult = WorkOrderV1Schema.safeParse(workOrder);
|
|
17
|
+
if (!parseResult.success) {
|
|
18
|
+
return {
|
|
19
|
+
success: false,
|
|
20
|
+
error: `Invalid work order: ${parseResult.error.message}`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const workOrderJson = JSON.stringify(workOrder);
|
|
24
|
+
const overrideFlags = [];
|
|
25
|
+
const pushBool = (name, value) => {
|
|
26
|
+
if (typeof value !== "boolean")
|
|
27
|
+
return;
|
|
28
|
+
overrideFlags.push(value ? `--${name}` : `--no-${name}`);
|
|
29
|
+
};
|
|
30
|
+
pushBool("fail-fast", options.failFast);
|
|
31
|
+
pushBool("semgrep", options.semgrep);
|
|
32
|
+
pushBool("runtime", options.runtime);
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
const child = spawn(pythonExe, [
|
|
35
|
+
"-m",
|
|
36
|
+
"vibecoding_helper",
|
|
37
|
+
"--repo-root",
|
|
38
|
+
repoRoot,
|
|
39
|
+
"gate-check",
|
|
40
|
+
...overrideFlags,
|
|
41
|
+
"--work-order",
|
|
42
|
+
workOrderJson,
|
|
43
|
+
"--format",
|
|
44
|
+
"json",
|
|
45
|
+
], {
|
|
46
|
+
cwd: repoRoot,
|
|
47
|
+
timeout: timeoutMs,
|
|
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
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Execute a work order only if it passes the gate
|
|
106
|
+
*
|
|
107
|
+
* @throws Error if gate blocks the work order
|
|
108
|
+
*/
|
|
109
|
+
export async function executeWithGate(workOrder, options) {
|
|
110
|
+
const gateResult = await runSystemDesignGate(workOrder, options);
|
|
111
|
+
if (!gateResult.success || !gateResult.result) {
|
|
112
|
+
throw new Error(`Gate check failed: ${gateResult.error || "Unknown error"}`);
|
|
113
|
+
}
|
|
114
|
+
if (gateResult.result.verdict === "BLOCK") {
|
|
115
|
+
const violations = gateResult.result.violations
|
|
116
|
+
.map((v) => `[${v.rule_id}] ${v.message}`)
|
|
117
|
+
.join("; ");
|
|
118
|
+
throw new Error(`Gate BLOCKED: ${violations}`);
|
|
119
|
+
}
|
|
120
|
+
return gateResult.result;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Check if a work order would pass the gate (without executing)
|
|
124
|
+
*/
|
|
125
|
+
export async function checkGate(workOrder, options) {
|
|
126
|
+
const gateResult = await runSystemDesignGate(workOrder, options);
|
|
127
|
+
if (!gateResult.success || !gateResult.result) {
|
|
128
|
+
return {
|
|
129
|
+
allowed: false,
|
|
130
|
+
result: null,
|
|
131
|
+
error: gateResult.error,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
allowed: gateResult.result.verdict === "ALLOW",
|
|
136
|
+
result: gateResult.result,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Format gate violations for display
|
|
141
|
+
*/
|
|
142
|
+
export function formatViolations(result) {
|
|
143
|
+
if (result.violations.length === 0) {
|
|
144
|
+
return "No violations";
|
|
145
|
+
}
|
|
146
|
+
const lines = result.violations.map((v) => {
|
|
147
|
+
const emoji = v.level === "BLOCK" ? "โ" : v.level === "WARN" ? "โ ๏ธ" : "โน๏ธ";
|
|
148
|
+
let line = `${emoji} [${v.rule_id}] ${v.message}`;
|
|
149
|
+
if (v.remediation) {
|
|
150
|
+
line += `\n Fix: ${v.remediation}`;
|
|
151
|
+
}
|
|
152
|
+
if (v.location?.file) {
|
|
153
|
+
line += `\n Location: ${v.location.file}`;
|
|
154
|
+
if (v.location.line) {
|
|
155
|
+
line += `:${v.location.line}`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return line;
|
|
159
|
+
});
|
|
160
|
+
return lines.join("\n\n");
|
|
161
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/dx/activity.ts
|
|
2
|
+
// Activity/DX helpers: branded header, status icons, optional desktop notifications
|
|
3
|
+
function boolEnv(name, defaultValue) {
|
|
4
|
+
const raw = (process.env[name] ?? "").trim().toLowerCase();
|
|
5
|
+
if (!raw)
|
|
6
|
+
return defaultValue;
|
|
7
|
+
if (["1", "true", "yes", "y", "on"].includes(raw))
|
|
8
|
+
return true;
|
|
9
|
+
if (["0", "false", "no", "n", "off"].includes(raw))
|
|
10
|
+
return false;
|
|
11
|
+
return defaultValue;
|
|
12
|
+
}
|
|
13
|
+
function intEnv(name, defaultValue) {
|
|
14
|
+
const raw = (process.env[name] ?? "").trim();
|
|
15
|
+
if (!raw)
|
|
16
|
+
return defaultValue;
|
|
17
|
+
const n = Number(raw);
|
|
18
|
+
return Number.isFinite(n) ? Math.trunc(n) : defaultValue;
|
|
19
|
+
}
|
|
20
|
+
export function isActivityHeaderEnabled() {
|
|
21
|
+
return boolEnv("VIBECODE_ACTIVITY_HEADER", true);
|
|
22
|
+
}
|
|
23
|
+
export function isDesktopNotifyEnabled() {
|
|
24
|
+
return boolEnv("VIBECODE_NOTIFY", false);
|
|
25
|
+
}
|
|
26
|
+
export function desktopNotifyMinMs() {
|
|
27
|
+
return intEnv("VIBECODE_NOTIFY_MIN_MS", 1500);
|
|
28
|
+
}
|
|
29
|
+
function toolBadge(toolName, data) {
|
|
30
|
+
const map = {
|
|
31
|
+
"vibe_pm.briefing": { icon: "๐งญ", label: "ํ๋ก์ ํธ ์ ์" },
|
|
32
|
+
"vibe_pm.get_decision": { icon: "๐ณ๏ธ", label: "๊ฒฐ์ฌ ํ์ธ" },
|
|
33
|
+
"vibe_pm.submit_decision": { icon: "โ๏ธ", label: "๊ฒฐ์ฌ ๋ฐ์" },
|
|
34
|
+
"vibe_pm.create_work_order": { icon: "๐งพ", label: "์์
์ง์์ ์์ฑ" },
|
|
35
|
+
"vibe_pm.inspect_code": { icon: "๐", label: "๊ฒ์" },
|
|
36
|
+
"vibe_pm.repair_plan": { icon: "๐ ๏ธ", label: "์์ ์ง์์" },
|
|
37
|
+
"vibe_pm.status": { icon: "๐", label: "์ํ ํ์ธ" },
|
|
38
|
+
"vibe_pm.scaffold": { icon: "๐งฑ", label: "๊ตฌ์กฐ ์์ฑ" },
|
|
39
|
+
"vibe_pm.finalize_work": { icon: "โ
", label: "๋ง๊ฐ ์ ๋ฆฌ" },
|
|
40
|
+
"vibe_pm.undo_last_task": { icon: "โฉ๏ธ", label: "๋กค๋ฐฑ" },
|
|
41
|
+
"vibe_pm.run_app": { icon: "๐", label: "์คํ" },
|
|
42
|
+
"vibe_pm.export_output": { icon: "๐ฆ", label: "๋ด๋ณด๋ด๊ธฐ" },
|
|
43
|
+
"vibe_pm.search_oss": { icon: "๐", label: "๊ฒ์" },
|
|
44
|
+
"vibe_pm.zoekt_evidence": { icon: "๐งพ", label: "๊ทผ๊ฑฐ ์์ง" },
|
|
45
|
+
"vibe_pm.gate": { icon: "๐ก๏ธ", label: "์์ ์ ๊ฒ" },
|
|
46
|
+
"vibe_pm.doctor": { icon: "๐ฉบ", label: "์ง๋จ" },
|
|
47
|
+
"vibe_pm.update": { icon: "โฌ๏ธ", label: "์
๋ฐ์ดํธ" },
|
|
48
|
+
"vibe_pm.activate": { icon: "๐", label: "ํ์ฑํ" },
|
|
49
|
+
"vibe_pm.memory_status": { icon: "๐ง ", label: "๋ฉ๋ชจ๋ฆฌ ์ํ" },
|
|
50
|
+
"vibe_pm.memory_sync": { icon: "๐ง ", label: "๋ฉ๋ชจ๋ฆฌ ๋๊ธฐํ" },
|
|
51
|
+
"vibe_pm.advisory_review": { icon: "๐งช", label: "์ฌ์ ์ ๊ฒ" },
|
|
52
|
+
"react_perf.check_patterns": { icon: "โก๏ธ", label: "์ฑ๋ฅ ์ ๊ฒ" },
|
|
53
|
+
"react_perf.generate_report": { icon: "๐", label: "๋ฆฌํฌํธ ์์ฑ" }
|
|
54
|
+
};
|
|
55
|
+
if (toolName === "vibe_pm.run_app" && data && typeof data === "object") {
|
|
56
|
+
const obj = data;
|
|
57
|
+
const runtime = (obj.runtime ?? null);
|
|
58
|
+
const containerId = obj.container_id ?? runtime?.container_id;
|
|
59
|
+
const container = runtime?.container;
|
|
60
|
+
if (containerId || container === "podman") {
|
|
61
|
+
return { icon: "๐ฆ", label: "์๋๋ฐ์ค ์คํ" };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (toolName in map)
|
|
65
|
+
return map[toolName];
|
|
66
|
+
if (toolName.startsWith("vibe_pm."))
|
|
67
|
+
return { icon: "โก๏ธ", label: "์์
" };
|
|
68
|
+
return { icon: "โ๏ธ", label: "์์
" };
|
|
69
|
+
}
|
|
70
|
+
function verdictIcon(data, outcome) {
|
|
71
|
+
if (outcome === "error")
|
|
72
|
+
return "โ";
|
|
73
|
+
if (!data || typeof data !== "object")
|
|
74
|
+
return "โ
";
|
|
75
|
+
const obj = data;
|
|
76
|
+
const verdict = String(obj.verdict ?? "").toUpperCase();
|
|
77
|
+
if (verdict === "BLOCK")
|
|
78
|
+
return "โ";
|
|
79
|
+
if (verdict === "ALLOW")
|
|
80
|
+
return "โ
";
|
|
81
|
+
const reviewSummary = obj.review_summary;
|
|
82
|
+
if (reviewSummary && typeof reviewSummary === "object") {
|
|
83
|
+
const rs = reviewSummary;
|
|
84
|
+
const rr = String(rs.review_result ?? "").toUpperCase();
|
|
85
|
+
if (rr === "GO")
|
|
86
|
+
return "โ
";
|
|
87
|
+
if (rr === "FIX")
|
|
88
|
+
return "โ ๏ธ";
|
|
89
|
+
if (rr === "BLOCK")
|
|
90
|
+
return "โ";
|
|
91
|
+
}
|
|
92
|
+
return "โ
";
|
|
93
|
+
}
|
|
94
|
+
export function renderActivityHeader(toolName, data, outcome = "success", durationMs) {
|
|
95
|
+
const badge = toolBadge(toolName, data);
|
|
96
|
+
const status = verdictIcon(data, outcome);
|
|
97
|
+
const duration = typeof durationMs === "number" && durationMs >= 0 ? ` ยท ${Math.round(durationMs)}ms` : "";
|
|
98
|
+
return [
|
|
99
|
+
"โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ",
|
|
100
|
+
"โ VibePM โก๏ธ Active โ",
|
|
101
|
+
`โ ${status} ${badge.icon} ${badge.label}${duration}`,
|
|
102
|
+
"โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ"
|
|
103
|
+
].join("\n");
|
|
104
|
+
}
|
|
105
|
+
export function decorateToolOutputText(toolName, jsonText, data, outcome = "success", durationMs) {
|
|
106
|
+
if (!isActivityHeaderEnabled()) {
|
|
107
|
+
return [{ type: "text", text: jsonText }];
|
|
108
|
+
}
|
|
109
|
+
const header = renderActivityHeader(toolName, data, outcome, durationMs);
|
|
110
|
+
return [
|
|
111
|
+
{ type: "text", text: header },
|
|
112
|
+
{ type: "text", text: jsonText }
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
export async function notifyDesktopBestEffort(args) {
|
|
116
|
+
if (!isDesktopNotifyEnabled())
|
|
117
|
+
return;
|
|
118
|
+
const minMs = desktopNotifyMinMs();
|
|
119
|
+
const durationMs = args.durationMs ?? 0;
|
|
120
|
+
if (args.outcome !== "error" && minMs > 0 && durationMs < minMs)
|
|
121
|
+
return;
|
|
122
|
+
try {
|
|
123
|
+
const mod = await import("node-notifier");
|
|
124
|
+
const notifier = mod.default ?? mod;
|
|
125
|
+
const badge = toolBadge(args.toolName, args.data);
|
|
126
|
+
const status = verdictIcon(args.data, args.outcome);
|
|
127
|
+
const duration = durationMs ? ` (${Math.round(durationMs)}ms)` : "";
|
|
128
|
+
notifier.notify({
|
|
129
|
+
title: "VibePM โก๏ธ",
|
|
130
|
+
message: `${status} ${badge.icon} ${badge.label}${duration}`,
|
|
131
|
+
sound: false,
|
|
132
|
+
wait: false,
|
|
133
|
+
timeout: 3
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// Best-effort: never fail tool execution due to notify issues (headless/permissions).
|
|
138
|
+
}
|
|
139
|
+
}
|