@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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// adapters/mcp-ts/src/tools/vibe_pm/pm_language.ts
|
|
2
2
|
// PM Language transformation utilities
|
|
3
3
|
// Reference: docs/DEV_SPEC/MCP_TOOL_SCHEMA_SPEC.md Section: Status Mapping
|
|
4
|
+
import { applyTermDictionary, resolveLocaleFromText } from "./waiter_mapping.js";
|
|
4
5
|
// ============================================================
|
|
5
6
|
// Signal Level Mapping (Internal → User-facing)
|
|
6
7
|
// ============================================================
|
|
@@ -202,21 +203,21 @@ ${verifyList}
|
|
|
202
203
|
* This is a simplified version - full implementation would parse and restructure
|
|
203
204
|
*/
|
|
204
205
|
export function transformAskQueueText(rawText) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
206
|
+
try {
|
|
207
|
+
const locale = resolveLocaleFromText(rawText);
|
|
208
|
+
return applyTermDictionary(rawText, locale);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// Best-effort fallback: preserve previous behavior when mapping is unavailable.
|
|
212
|
+
return rawText
|
|
213
|
+
.replace(/run_id/gi, "프로젝트")
|
|
214
|
+
.replace(/bridge/gi, "작업 지시서")
|
|
215
|
+
.replace(/schema/gi, "구조")
|
|
216
|
+
.replace(/gate/gi, "검토")
|
|
217
|
+
.replace(/signal/gi, "상태")
|
|
218
|
+
.replace(/FSM/gi, "워크플로우")
|
|
219
|
+
.replace(/spec_drift/gi, "범위 변경")
|
|
220
|
+
.replace(/decision_card/gi, "결재 안건")
|
|
221
|
+
.replace(/clinic_bridge/gi, "검수 기준");
|
|
220
222
|
}
|
|
221
|
-
return transformed;
|
|
222
223
|
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/tools/vibe_pm/pre_commit_analysis.ts
|
|
2
|
+
// Pre-commit analysis: categorize changes and suggest staging strategy
|
|
3
|
+
import { simpleGit } from "simple-git";
|
|
4
|
+
/**
|
|
5
|
+
* Categorize a file based on its path
|
|
6
|
+
*/
|
|
7
|
+
function categorizeFile(filePath) {
|
|
8
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
9
|
+
// MCP TypeScript
|
|
10
|
+
if (normalized.includes("adapters/mcp-ts/src/"))
|
|
11
|
+
return "mcp_source";
|
|
12
|
+
if (normalized.includes("adapters/mcp-ts/build/"))
|
|
13
|
+
return "mcp_build";
|
|
14
|
+
if (normalized.includes("adapters/mcp-ts/test"))
|
|
15
|
+
return "mcp_test";
|
|
16
|
+
if (normalized.includes("adapters/mcp-ts/"))
|
|
17
|
+
return "mcp_other";
|
|
18
|
+
// Engines (Rust)
|
|
19
|
+
if (normalized.includes("engines/") && normalized.endsWith(".rs"))
|
|
20
|
+
return "engine_rust";
|
|
21
|
+
if (normalized.includes("engines/"))
|
|
22
|
+
return "engine_other";
|
|
23
|
+
// Documentation
|
|
24
|
+
if (normalized.includes("docs/"))
|
|
25
|
+
return "docs";
|
|
26
|
+
if (normalized.endsWith(".md"))
|
|
27
|
+
return "docs";
|
|
28
|
+
// Python
|
|
29
|
+
if (normalized.includes("vibecoding_helper/"))
|
|
30
|
+
return "python_core";
|
|
31
|
+
if (normalized.endsWith(".py"))
|
|
32
|
+
return "python";
|
|
33
|
+
// Config/CI
|
|
34
|
+
if (normalized.includes(".github/"))
|
|
35
|
+
return "ci";
|
|
36
|
+
if (normalized.includes(".vibe/"))
|
|
37
|
+
return "vibe_config";
|
|
38
|
+
if (normalized.includes("schemas/"))
|
|
39
|
+
return "schemas";
|
|
40
|
+
if (normalized.endsWith(".json") ||
|
|
41
|
+
normalized.endsWith(".yaml") ||
|
|
42
|
+
normalized.endsWith(".yml") ||
|
|
43
|
+
normalized.endsWith(".toml"))
|
|
44
|
+
return "config";
|
|
45
|
+
// Generated
|
|
46
|
+
if (normalized.includes("/generated/"))
|
|
47
|
+
return "generated";
|
|
48
|
+
// Tests
|
|
49
|
+
if (normalized.includes("/test") || normalized.includes("/tests/"))
|
|
50
|
+
return "tests";
|
|
51
|
+
// Build artifacts
|
|
52
|
+
if (normalized.includes("/dist/") ||
|
|
53
|
+
normalized.includes("/build/") ||
|
|
54
|
+
normalized.includes("/target/"))
|
|
55
|
+
return "build_artifacts";
|
|
56
|
+
// Lock files
|
|
57
|
+
if (normalized.endsWith("-lock.json") ||
|
|
58
|
+
normalized.endsWith(".lock") ||
|
|
59
|
+
normalized.endsWith("-lock.yaml"))
|
|
60
|
+
return "lock_files";
|
|
61
|
+
return "other";
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get human-readable category info
|
|
65
|
+
*/
|
|
66
|
+
function getCategoryInfo(category) {
|
|
67
|
+
const info = {
|
|
68
|
+
mcp_source: {
|
|
69
|
+
name: "MCP 소스 코드",
|
|
70
|
+
description: "TypeScript MCP 서버 소스 (빌드 후 npm 배포 필요할 수 있음)",
|
|
71
|
+
suggested: true,
|
|
72
|
+
},
|
|
73
|
+
mcp_build: {
|
|
74
|
+
name: "MCP 빌드 결과물",
|
|
75
|
+
description: "컴파일된 JavaScript (보통 gitignore됨)",
|
|
76
|
+
suggested: false,
|
|
77
|
+
},
|
|
78
|
+
mcp_test: {
|
|
79
|
+
name: "MCP 테스트",
|
|
80
|
+
description: "TypeScript 테스트 파일",
|
|
81
|
+
suggested: true,
|
|
82
|
+
},
|
|
83
|
+
mcp_other: {
|
|
84
|
+
name: "MCP 기타",
|
|
85
|
+
description: "MCP 패키지 설정, 스크립트 등",
|
|
86
|
+
suggested: true,
|
|
87
|
+
},
|
|
88
|
+
engine_rust: {
|
|
89
|
+
name: "Rust 엔진 코드",
|
|
90
|
+
description: "엔진 소스 (빌드 후 GitHub Release 필요할 수 있음)",
|
|
91
|
+
suggested: true,
|
|
92
|
+
},
|
|
93
|
+
engine_other: {
|
|
94
|
+
name: "엔진 기타",
|
|
95
|
+
description: "엔진 설정, 문서 등",
|
|
96
|
+
suggested: true,
|
|
97
|
+
},
|
|
98
|
+
docs: {
|
|
99
|
+
name: "문서",
|
|
100
|
+
description: "마크다운 문서, 스펙",
|
|
101
|
+
suggested: true,
|
|
102
|
+
},
|
|
103
|
+
python_core: {
|
|
104
|
+
name: "Python 코어",
|
|
105
|
+
description: "vibecoding_helper 모듈",
|
|
106
|
+
suggested: true,
|
|
107
|
+
},
|
|
108
|
+
python: {
|
|
109
|
+
name: "Python 기타",
|
|
110
|
+
description: "Python 스크립트, 테스트",
|
|
111
|
+
suggested: true,
|
|
112
|
+
},
|
|
113
|
+
ci: {
|
|
114
|
+
name: "CI/CD",
|
|
115
|
+
description: "GitHub Actions 워크플로우",
|
|
116
|
+
suggested: true,
|
|
117
|
+
},
|
|
118
|
+
vibe_config: {
|
|
119
|
+
name: "Vibe 설정",
|
|
120
|
+
description: ".vibe/ 로컬 설정 (보통 gitignore됨)",
|
|
121
|
+
suggested: false,
|
|
122
|
+
},
|
|
123
|
+
schemas: {
|
|
124
|
+
name: "스키마",
|
|
125
|
+
description: "JSON Schema 정의",
|
|
126
|
+
suggested: true,
|
|
127
|
+
},
|
|
128
|
+
config: {
|
|
129
|
+
name: "설정 파일",
|
|
130
|
+
description: "JSON, YAML, TOML 설정",
|
|
131
|
+
suggested: true,
|
|
132
|
+
},
|
|
133
|
+
generated: {
|
|
134
|
+
name: "생성된 코드",
|
|
135
|
+
description: "스키마에서 자동 생성된 코드 (재생성 가능)",
|
|
136
|
+
suggested: false,
|
|
137
|
+
},
|
|
138
|
+
tests: {
|
|
139
|
+
name: "테스트",
|
|
140
|
+
description: "테스트 파일",
|
|
141
|
+
suggested: true,
|
|
142
|
+
},
|
|
143
|
+
build_artifacts: {
|
|
144
|
+
name: "빌드 결과물",
|
|
145
|
+
description: "컴파일된 파일 (보통 gitignore됨)",
|
|
146
|
+
suggested: false,
|
|
147
|
+
},
|
|
148
|
+
lock_files: {
|
|
149
|
+
name: "Lock 파일",
|
|
150
|
+
description: "의존성 잠금 파일",
|
|
151
|
+
suggested: true,
|
|
152
|
+
},
|
|
153
|
+
other: {
|
|
154
|
+
name: "기타",
|
|
155
|
+
description: "분류되지 않은 파일",
|
|
156
|
+
suggested: false,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
return info[category] || info.other;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Analyze git status and categorize changes
|
|
163
|
+
*/
|
|
164
|
+
export async function analyzePreCommit(basePath = process.cwd()) {
|
|
165
|
+
const git = simpleGit({ baseDir: basePath });
|
|
166
|
+
const status = await git.status();
|
|
167
|
+
// Collect all changed files
|
|
168
|
+
const changes = [];
|
|
169
|
+
for (const file of status.not_added) {
|
|
170
|
+
changes.push({ path: file, status: "new", staged: false });
|
|
171
|
+
}
|
|
172
|
+
for (const file of status.created) {
|
|
173
|
+
changes.push({ path: file, status: "new", staged: true });
|
|
174
|
+
}
|
|
175
|
+
for (const file of status.modified) {
|
|
176
|
+
changes.push({ path: file, status: "modified", staged: false });
|
|
177
|
+
}
|
|
178
|
+
for (const file of status.staged) {
|
|
179
|
+
if (!changes.find((c) => c.path === file)) {
|
|
180
|
+
changes.push({ path: file, status: "modified", staged: true });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
for (const file of status.deleted) {
|
|
184
|
+
changes.push({ path: file, status: "deleted", staged: false });
|
|
185
|
+
}
|
|
186
|
+
for (const file of status.renamed) {
|
|
187
|
+
changes.push({ path: file.to, status: "renamed", staged: true });
|
|
188
|
+
}
|
|
189
|
+
// Group by category
|
|
190
|
+
const categoryMap = new Map();
|
|
191
|
+
for (const change of changes) {
|
|
192
|
+
const cat = categorizeFile(change.path);
|
|
193
|
+
if (!categoryMap.has(cat)) {
|
|
194
|
+
categoryMap.set(cat, []);
|
|
195
|
+
}
|
|
196
|
+
categoryMap.get(cat).push(change);
|
|
197
|
+
}
|
|
198
|
+
// Build categories
|
|
199
|
+
const categories = [];
|
|
200
|
+
for (const [cat, files] of categoryMap) {
|
|
201
|
+
const info = getCategoryInfo(cat);
|
|
202
|
+
categories.push({
|
|
203
|
+
name: info.name,
|
|
204
|
+
description: info.description,
|
|
205
|
+
files,
|
|
206
|
+
suggested: info.suggested,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
// Sort: suggested first, then by file count
|
|
210
|
+
categories.sort((a, b) => {
|
|
211
|
+
if (a.suggested !== b.suggested)
|
|
212
|
+
return a.suggested ? -1 : 1;
|
|
213
|
+
return b.files.length - a.files.length;
|
|
214
|
+
});
|
|
215
|
+
// Generate suggestions
|
|
216
|
+
const suggestions = [];
|
|
217
|
+
const warnings = [];
|
|
218
|
+
// Check for MCP source changes
|
|
219
|
+
const mcpSource = categoryMap.get("mcp_source");
|
|
220
|
+
if (mcpSource && mcpSource.length > 0) {
|
|
221
|
+
suggestions.push("MCP 소스 변경됨 → 커밋 후 `vibe_pm.publish_mcp` 실행 권장");
|
|
222
|
+
}
|
|
223
|
+
// Check for engine changes
|
|
224
|
+
const engineRust = categoryMap.get("engine_rust");
|
|
225
|
+
if (engineRust && engineRust.length > 0) {
|
|
226
|
+
suggestions.push("Rust 엔진 변경됨 → 커밋 후 엔진 빌드 + GitHub Release 필요할 수 있음");
|
|
227
|
+
}
|
|
228
|
+
// Check for vibe config
|
|
229
|
+
const vibeConfig = categoryMap.get("vibe_config");
|
|
230
|
+
if (vibeConfig && vibeConfig.length > 0) {
|
|
231
|
+
warnings.push(".vibe/ 파일은 보통 gitignore됩니다. 의도적 커밋인지 확인하세요.");
|
|
232
|
+
}
|
|
233
|
+
// Check for build artifacts
|
|
234
|
+
const buildArtifacts = categoryMap.get("build_artifacts");
|
|
235
|
+
if (buildArtifacts && buildArtifacts.length > 0) {
|
|
236
|
+
warnings.push("빌드 결과물은 보통 gitignore됩니다. 의도적 커밋인지 확인하세요.");
|
|
237
|
+
}
|
|
238
|
+
// Check for sensitive patterns
|
|
239
|
+
for (const change of changes) {
|
|
240
|
+
const lower = change.path.toLowerCase();
|
|
241
|
+
if (lower.includes("secret") ||
|
|
242
|
+
lower.includes("credential") ||
|
|
243
|
+
lower.includes(".env") ||
|
|
244
|
+
lower.includes("token")) {
|
|
245
|
+
warnings.push(`민감한 파일 감지: ${change.path} - 커밋 전 확인 필요`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
categories,
|
|
250
|
+
total_files: changes.length,
|
|
251
|
+
suggestions,
|
|
252
|
+
warnings,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Generate a summary for user decision
|
|
257
|
+
*/
|
|
258
|
+
export function formatAnalysisSummary(analysis) {
|
|
259
|
+
const lines = [];
|
|
260
|
+
lines.push(`## 변경 파일 분석 (총 ${analysis.total_files}개)\n`);
|
|
261
|
+
for (const cat of analysis.categories) {
|
|
262
|
+
const emoji = cat.suggested ? "✅" : "⚠️";
|
|
263
|
+
lines.push(`### ${emoji} ${cat.name} (${cat.files.length}개)`);
|
|
264
|
+
lines.push(`> ${cat.description}`);
|
|
265
|
+
lines.push("");
|
|
266
|
+
// Show up to 5 files
|
|
267
|
+
const shown = cat.files.slice(0, 5);
|
|
268
|
+
for (const f of shown) {
|
|
269
|
+
const statusIcon = f.staged ? "📦" : "📝";
|
|
270
|
+
lines.push(`- ${statusIcon} ${f.path}`);
|
|
271
|
+
}
|
|
272
|
+
if (cat.files.length > 5) {
|
|
273
|
+
lines.push(`- ... 외 ${cat.files.length - 5}개`);
|
|
274
|
+
}
|
|
275
|
+
lines.push("");
|
|
276
|
+
}
|
|
277
|
+
if (analysis.suggestions.length > 0) {
|
|
278
|
+
lines.push("### 💡 제안");
|
|
279
|
+
for (const s of analysis.suggestions) {
|
|
280
|
+
lines.push(`- ${s}`);
|
|
281
|
+
}
|
|
282
|
+
lines.push("");
|
|
283
|
+
}
|
|
284
|
+
if (analysis.warnings.length > 0) {
|
|
285
|
+
lines.push("### ⚠️ 경고");
|
|
286
|
+
for (const w of analysis.warnings) {
|
|
287
|
+
lines.push(`- ${w}`);
|
|
288
|
+
}
|
|
289
|
+
lines.push("");
|
|
290
|
+
}
|
|
291
|
+
return lines.join("\n");
|
|
292
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
// adapters/mcp-ts/src/tools/vibe_pm/publish_mcp.ts
|
|
2
|
+
// MCP build and publish workflow
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { getCredentialStore } from "../../auth/credential_store.js";
|
|
7
|
+
/**
|
|
8
|
+
* Check if MCP source files have changed since last build
|
|
9
|
+
*/
|
|
10
|
+
async function hasSourceChanges(mcpRoot) {
|
|
11
|
+
const srcDir = path.join(mcpRoot, "src");
|
|
12
|
+
const buildDir = path.join(mcpRoot, "build");
|
|
13
|
+
// If no build folder, definitely changed
|
|
14
|
+
if (!fs.existsSync(buildDir)) {
|
|
15
|
+
return { changed: true, files: ["build/ not found"] };
|
|
16
|
+
}
|
|
17
|
+
// Get build timestamp
|
|
18
|
+
const buildIndex = path.join(buildDir, "index.js");
|
|
19
|
+
if (!fs.existsSync(buildIndex)) {
|
|
20
|
+
return { changed: true, files: ["build/index.js not found"] };
|
|
21
|
+
}
|
|
22
|
+
const buildStat = fs.statSync(buildIndex);
|
|
23
|
+
const buildTime = buildStat.mtime.getTime();
|
|
24
|
+
// Find source files newer than build
|
|
25
|
+
const changedFiles = [];
|
|
26
|
+
function checkDir(dir) {
|
|
27
|
+
if (!fs.existsSync(dir))
|
|
28
|
+
return;
|
|
29
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
const fullPath = path.join(dir, entry.name);
|
|
32
|
+
if (entry.isDirectory()) {
|
|
33
|
+
checkDir(fullPath);
|
|
34
|
+
}
|
|
35
|
+
else if (entry.name.endsWith(".ts")) {
|
|
36
|
+
const stat = fs.statSync(fullPath);
|
|
37
|
+
if (stat.mtime.getTime() > buildTime) {
|
|
38
|
+
changedFiles.push(path.relative(mcpRoot, fullPath));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
checkDir(srcDir);
|
|
44
|
+
return { changed: changedFiles.length > 0, files: changedFiles };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Run TypeScript build
|
|
48
|
+
*/
|
|
49
|
+
function runBuild(mcpRoot) {
|
|
50
|
+
try {
|
|
51
|
+
const result = spawnSync("npm", ["run", "build"], {
|
|
52
|
+
cwd: mcpRoot,
|
|
53
|
+
encoding: "utf-8",
|
|
54
|
+
shell: true,
|
|
55
|
+
timeout: 120_000, // 2 minutes
|
|
56
|
+
});
|
|
57
|
+
if (result.status === 0) {
|
|
58
|
+
return { success: true, output: result.stdout || "" };
|
|
59
|
+
}
|
|
60
|
+
return { success: false, output: result.stderr || result.stdout || "Build failed" };
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
return { success: false, output: e instanceof Error ? e.message : String(e) };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Bump version in package.json
|
|
68
|
+
*/
|
|
69
|
+
function bumpVersion(mcpRoot, type) {
|
|
70
|
+
try {
|
|
71
|
+
const result = spawnSync("npm", ["version", type, "--no-git-tag-version"], {
|
|
72
|
+
cwd: mcpRoot,
|
|
73
|
+
encoding: "utf-8",
|
|
74
|
+
shell: true,
|
|
75
|
+
});
|
|
76
|
+
if (result.status === 0) {
|
|
77
|
+
// Read new version from package.json
|
|
78
|
+
const pkgPath = path.join(mcpRoot, "package.json");
|
|
79
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
80
|
+
return pkg.version;
|
|
81
|
+
}
|
|
82
|
+
throw new Error(result.stderr || "Version bump failed");
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
throw new Error(`Version bump failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Run npm publish
|
|
90
|
+
*/
|
|
91
|
+
async function runPublish(mcpRoot, dryRun) {
|
|
92
|
+
const store = getCredentialStore();
|
|
93
|
+
const npmToken = await store.getNpmToken();
|
|
94
|
+
// Set NPM_TOKEN if we have one stored
|
|
95
|
+
const env = { ...process.env };
|
|
96
|
+
if (npmToken) {
|
|
97
|
+
env.NPM_TOKEN = npmToken;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const args = ["publish", "--access", "public"];
|
|
101
|
+
if (dryRun) {
|
|
102
|
+
args.push("--dry-run");
|
|
103
|
+
}
|
|
104
|
+
const result = spawnSync("npm", args, {
|
|
105
|
+
cwd: mcpRoot,
|
|
106
|
+
encoding: "utf-8",
|
|
107
|
+
shell: true,
|
|
108
|
+
env,
|
|
109
|
+
timeout: 120_000, // 2 minutes
|
|
110
|
+
});
|
|
111
|
+
if (result.status === 0) {
|
|
112
|
+
return { success: true, output: result.stdout || "" };
|
|
113
|
+
}
|
|
114
|
+
return { success: false, output: result.stderr || result.stdout || "Publish failed" };
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
return { success: false, output: e instanceof Error ? e.message : String(e) };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* vibe_pm.publish_mcp - Build and publish MCP package
|
|
122
|
+
*
|
|
123
|
+
* Workflow:
|
|
124
|
+
* 1. Check for source changes (skip if no changes unless --force)
|
|
125
|
+
* 2. Run npm build (tsc)
|
|
126
|
+
* 3. Optionally bump version
|
|
127
|
+
* 4. Run npm publish
|
|
128
|
+
*
|
|
129
|
+
* PM-friendly description:
|
|
130
|
+
* MCP 패키지를 빌드하고 npm에 배포합니다.
|
|
131
|
+
*/
|
|
132
|
+
export async function publishMcp(input, basePath = process.cwd()) {
|
|
133
|
+
const warnings = [];
|
|
134
|
+
// Find MCP root (adapters/mcp-ts)
|
|
135
|
+
const mcpRoot = path.join(basePath, "adapters", "mcp-ts");
|
|
136
|
+
if (!fs.existsSync(path.join(mcpRoot, "package.json"))) {
|
|
137
|
+
return {
|
|
138
|
+
success: false,
|
|
139
|
+
phase: "check",
|
|
140
|
+
version: "unknown",
|
|
141
|
+
built: false,
|
|
142
|
+
published: false,
|
|
143
|
+
message: "MCP 패키지를 찾을 수 없습니다: adapters/mcp-ts/package.json",
|
|
144
|
+
warnings,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
// Read current version
|
|
148
|
+
const pkgPath = path.join(mcpRoot, "package.json");
|
|
149
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
150
|
+
let version = pkg.version;
|
|
151
|
+
// Phase 1: Check for changes
|
|
152
|
+
const { changed, files } = await hasSourceChanges(mcpRoot);
|
|
153
|
+
if (!changed && !input.force && !input.skip_build) {
|
|
154
|
+
return {
|
|
155
|
+
success: true,
|
|
156
|
+
phase: "check",
|
|
157
|
+
version,
|
|
158
|
+
built: false,
|
|
159
|
+
published: false,
|
|
160
|
+
message: "소스 변경 없음. 빌드 스킵.",
|
|
161
|
+
warnings: ["--force로 강제 빌드 가능"],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// Phase 2: Build
|
|
165
|
+
let built = false;
|
|
166
|
+
if (!input.skip_build) {
|
|
167
|
+
const buildResult = runBuild(mcpRoot);
|
|
168
|
+
if (!buildResult.success) {
|
|
169
|
+
return {
|
|
170
|
+
success: false,
|
|
171
|
+
phase: "build",
|
|
172
|
+
version,
|
|
173
|
+
built: false,
|
|
174
|
+
published: false,
|
|
175
|
+
message: `빌드 실패: ${buildResult.output}`,
|
|
176
|
+
warnings,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
built = true;
|
|
180
|
+
if (files.length > 0) {
|
|
181
|
+
warnings.push(`변경된 파일: ${files.slice(0, 5).join(", ")}${files.length > 5 ? ` 외 ${files.length - 5}개` : ""}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Build only mode
|
|
185
|
+
if (input.build_only) {
|
|
186
|
+
return {
|
|
187
|
+
success: true,
|
|
188
|
+
phase: "build",
|
|
189
|
+
version,
|
|
190
|
+
built,
|
|
191
|
+
published: false,
|
|
192
|
+
message: `빌드 완료 (v${version})`,
|
|
193
|
+
warnings,
|
|
194
|
+
next_action: {
|
|
195
|
+
tool: "vibe_pm.publish_mcp",
|
|
196
|
+
reason: "배포하려면 build_only: false로 다시 실행",
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
// Phase 3: Version bump (optional)
|
|
201
|
+
if (input.bump) {
|
|
202
|
+
try {
|
|
203
|
+
version = bumpVersion(mcpRoot, input.bump);
|
|
204
|
+
warnings.push(`버전 범프: ${pkg.version} → ${version}`);
|
|
205
|
+
}
|
|
206
|
+
catch (e) {
|
|
207
|
+
return {
|
|
208
|
+
success: false,
|
|
209
|
+
phase: "publish",
|
|
210
|
+
version,
|
|
211
|
+
built,
|
|
212
|
+
published: false,
|
|
213
|
+
message: e instanceof Error ? e.message : String(e),
|
|
214
|
+
warnings,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Phase 4: Publish
|
|
219
|
+
const publishResult = await runPublish(mcpRoot, input.dry_run ?? false);
|
|
220
|
+
if (!publishResult.success) {
|
|
221
|
+
// Check if it's an auth error
|
|
222
|
+
if (publishResult.output.includes("ENEEDAUTH") || publishResult.output.includes("401")) {
|
|
223
|
+
return {
|
|
224
|
+
success: false,
|
|
225
|
+
phase: "publish",
|
|
226
|
+
version,
|
|
227
|
+
built,
|
|
228
|
+
published: false,
|
|
229
|
+
message: "npm 인증 실패. NPM_TOKEN을 설정하거나 npm login을 실행하세요.",
|
|
230
|
+
warnings: [
|
|
231
|
+
"vibe config set npm_token <TOKEN> 으로 토큰 저장 가능",
|
|
232
|
+
"또는 NPM_TOKEN 환경변수 설정",
|
|
233
|
+
],
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
success: false,
|
|
238
|
+
phase: "publish",
|
|
239
|
+
version,
|
|
240
|
+
built,
|
|
241
|
+
published: false,
|
|
242
|
+
message: `배포 실패: ${publishResult.output}`,
|
|
243
|
+
warnings,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const dryRunMsg = input.dry_run ? " (dry-run)" : "";
|
|
247
|
+
return {
|
|
248
|
+
success: true,
|
|
249
|
+
phase: "complete",
|
|
250
|
+
version,
|
|
251
|
+
built,
|
|
252
|
+
published: !input.dry_run,
|
|
253
|
+
message: `@vibecodetown/mcp-server@${version} 배포 완료${dryRunMsg}`,
|
|
254
|
+
warnings,
|
|
255
|
+
next_action: {
|
|
256
|
+
tool: "vibe_pm.status",
|
|
257
|
+
reason: "배포 상태 확인",
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Quick check if MCP needs rebuild
|
|
263
|
+
*/
|
|
264
|
+
export async function needsMcpBuild(basePath = process.cwd()) {
|
|
265
|
+
const mcpRoot = path.join(basePath, "adapters", "mcp-ts");
|
|
266
|
+
if (!fs.existsSync(path.join(mcpRoot, "package.json"))) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
const { changed } = await hasSourceChanges(mcpRoot);
|
|
270
|
+
return changed;
|
|
271
|
+
}
|